Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot @Transactional not working when on @Service class (instead of controller)

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());
    }
}
like image 672
Jaap van Hengstum Avatar asked Jan 05 '16 15:01

Jaap van Hengstum


People also ask

Can we use @transactional in service class?

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.

Can we use @transactional in service layer?

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 ...

Can we use @transactional in controller?

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).

Is Spring @service transactional?

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.


1 Answers

@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);
    }

    ...
}
like image 120
Jaap van Hengstum Avatar answered Sep 19 '22 13:09

Jaap van Hengstum