8

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());
    }
}
Community
  • 1
  • 1
Jaap van Hengstum
  • 4,494
  • 4
  • 30
  • 34
  • What isn't working.... Also when using Spring Boot you don't need `@EnableTransactionManagement` Spring Boot already does that for you. – M. Deinum Jan 05 '16 at 15:22
  • When I put the `@Transactional` annotation on the service class (and not on the controller) there is no rollback when a RuntimeException occurs in the controller. When I put the `@Transactional` annotation on the controller class, the rollback _does_ occur. But it is not best practice to put `@Transactional` on controllers; so why does it only work when I put it on the controller? – Jaap van Hengstum Jan 05 '16 at 15:28
  • 1
    Ofcourse not... The transaction is already committed at that stage, so why should it rollback after it committed... Basically it works as designed... – M. Deinum Jan 05 '16 at 15:34
  • Ok, but why then do I read that the controller shouldn't be transactional but the services should, like here: http://stackoverflow.com/questions/23118789/spring-mvc-controller-shouldnt-be-transactional-why?lq=1 – Jaap van Hengstum Jan 05 '16 at 15:40
  • 1
    Because you shouldn't... The transactional layer is the service, your controller is nothing more then an integration layer and should be as thin as possible. What if you want to reuse the same logic for a web service, are you going to make that transactional? Or JMS, or.... You want your service to be the boundary everything else is integration. – M. Deinum Jan 05 '16 at 15:41

2 Answers2

4

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

    ...
}
Jaap van Hengstum
  • 4,494
  • 4
  • 30
  • 34
-4

You can place the @Transactional annotation before an interface definition, a method on an interface, a class definition, or a public method on a class. However, the mere presence of the @Transactional annotation is not enough to activate the transactional behavior.

The @Transactional annotation is simply metadata that can be consumed by some runtime infrastructure that is @Transactional-aware and that can use the metadata to configure the appropriate beans with transactional behavior.

In the preceding example, the <tx:annotation-driven/> element switches on the transactional behavior.

    // Below is the service class that we want to make transactional
    @Transactional
    public class DefaultEmployeeService implements EmployeeService {

        void insertEmployee(Employee Employee);

        void updateEmployee(Employee Employee);
    }

    XML Configuration:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" ...>

        <!-- this is the service object that we want to make transactional -->
        <bean id="employeeService" class="service.DefaultEmployeeService"/>

        <!-- enable the configuration of transactional behavior based on annotations -->
        <tx:annotation-driven transaction-manager="txManager"/>

        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- (this dependency is defined somewhere else) -->
            <property name="dataSource" ref="dataSource"/>
        </bean>
    </beans>

Some points that you can try:

  1. Do all above mentioned configurations.
  2. Remove @Transactional configuration from Controller.
  3. Reference the Service object along with Transaction manager bean.

More Details: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

Remi Guan
  • 21,506
  • 17
  • 64
  • 87
sachin k
  • 236
  • 2
  • 10