I thought it was best practice to put the @Transactional
annotation on the service layer classes and not on the controllers (see f.e. Why we shouldn't make a Spring MVC controller @Transactional?). But this not working on my Spring Boot application. Why is that?
The registerAction
method in the controller (see code below) performs multiple service calls. When f.e. the mailService.sendActivationMail(...)
fails, I want to rollback the inserted user from the userService.registerUser(...)
call. Do I need to put the @Transactional
annotation on the controller class or not?
My Spring Boot application correctly uses transactions when the @Transactional
annotation is set on the controller class:
AuthController.java
@RestController
@RequestMapping("/api/auth")
@Transactional
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private ProfileService profileService;
@Autowired
private MailService mailService;
@RequestMapping(path = "register", method = RequestMethod.POST)
public Profile registerAction(@Valid @RequestBody Registration registration) {
ApiUser user = userService.registerUser(registration);
Profile profile = profileService.createProfile(user, registration);
mailService.sendActivationMail(user);
return profile;
}
}
but transactions don't work when the @Transactional
annotation is set on the Service classes instead (and not on the controller):
UserService.java
@Service
@Transactional
public class UserService {
@Autowired
private ApiUserRepository userRepository;
public ApiUser registerUser(Registration registration) {
...
userRepository.save(user);
...
}
}
My configuration classes:
SpringApiApplication.java
@SpringBootApplication
public class SpringApiApplication {
public static void main(String[] args) {
SpringApplication.run(SpringApiCommonApplication.class, args);
}
}
ApiConfiguration.java
@Configuration
@EnableJpaAuditing
@EnableTransactionManagement
public class ApiConfiguration {
@Autowired
private ApiProperties properties;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UsernameCanonicalizer usernameCanonicalizer() {
return new UsernameCanonicalizer();
}
@Bean
public EmailCanonicalizer emailCanonicalizer() {
return new EmailCanonicalizer();
}
@Bean
public ApiTokenHandler activationTokenHandler() {
return new StandardApiTokenHandler(properties.getActivationTokenDuration());
}
}
Yes, you should always access the database from inside a transaction. Not doing it will in fact create a transaction for every select statements. Transactions aren't just useful for atomicity of updates. They also provide isolation guarantees.
You should use @Transactional at service layer, if you want to change the domain model for client B where you have to provide the same data in a different model,you can change the domain model without impacting the DAO layer by providing a different service or by creating a interface and implementing the interface in ...
The controller can be made @Transactional , but indeed it's a common recommendation to only make the service layer transactional (the persistence layer should not be transactional either).
At a high level, Spring creates proxies for all the classes annotated with @Transactional, either on the class or on any of the methods. The proxy allows the framework to inject transactional logic before and after the running method, mainly for starting and committing the transaction.
@M.Deinum got me on the right track. Spring (boot) doesn't automatically wrap your controller calls in a transaction like other frameworks do. So you have to either add a @Transactional
annotation to your controller, or move the code from the controller class to a service class.
Moving the code from the controller class to a service class is the better thing to do since (amongst other things) makes the code better testable. So that's what I did.
AuthenticationService.java
@Service
public class AuthenticationService {
@Autowired
private UserManager userManager;
@Autowired
private ProfileManager profileManager;
@Autowired
private MailManager mailManager;
@Transactional
public Profile registerUser(Registration registration) {
ApiUser user = userManager.registerUser(registration);
Profile profile = profileManager.createProfile(user, registration);
mailManager.sendActivationMail(user);
return profile;
}
...
}
AuthController.java
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationService authenticationService;
@RequestMapping(path = "register", method = RequestMethod.POST)
@ApiOperation(value = "Registers a user")
public Profile register(@Valid @RequestBody Registration registration) {
return authenticationService.registerUser(registration);
}
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With