【问题标题】:Spring Boot @Transactional not working when on @Service class (instead of controller)Spring Boot @Transactional 在 @Service 类(而不是控制器)上不起作用
【发布时间】:2016-01-05 15:06:16
【问题描述】:

我认为将@Transactional 注释放在服务层类上而不是控制器上是最佳实践(参见 f.e. Why we shouldn't make a Spring MVC controller @Transactional?)。但这不适用于我的 Spring Boot 应用程序。这是为什么呢?

控制器中的registerAction 方法(参见下面的代码)执行多个服务调用。当 f.e. mailService.sendActivationMail(...) 失败,我想从 userService.registerUser(...) 调用中回滚插入的用户。我是否需要将@Transactional 注释放在控制器类上?

当在控制器类上设置@Transactional 注解时,我的 Spring Boot 应用程序正确使用事务:

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

}

但是当 @Transactional 注解设置在服务类上(而不是控制器上)时,事务不起作用:

UserService.java

@Service
@Transactional
public class UserService {

    @Autowired
    private ApiUserRepository userRepository;

    public ApiUser registerUser(Registration registration) {
        ...
        userRepository.save(user);
        ...
    }

}

我的配置类:

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

【问题讨论】:

  • 什么不起作用....此外,使用 Spring Boot 时,您不需要 @EnableTransactionManagement Spring Boot 已经为您做到了。
  • 当我将@Transactional 注释放在服务类上(而不是在控制器上)时,当控制器中发生 RuntimeException 时不会回滚。当我将@Transactional 注释放在控制器类上时,会发生回滚确实。但是将@Transactional 放在控制器上并不是最佳做法;那为什么只有放在控制器上才有效呢?
  • 当然不是...事务在那个阶段已经提交了,为什么要在提交后回滚...基本上它按设计工作...
  • 好的,但是为什么我读到控制器不应该是事务性的,但服务应该是这样的:stackoverflow.com/questions/23118789/…
  • 因为你不应该......事务层是服务,你的控制器只不过是一个集成层,应该尽可能薄。如果您想为 Web 服务重用相同的逻辑怎么办,您是否要使该逻辑成为事务性的?或者 JMS,或者....您希望您的服务成为其他一切集成的边界。

标签: spring transactions spring-boot


【解决方案1】:

@M.Deinum 让我走上了正轨。 Spring(引导)不会像其他框架那样自动将您的控制器调用包装在事务中。因此,您必须向控制器添加@Transactional 注释,或者将代码从控制器类移动到服务类。

将代码从控制器类移动到服务类是更好的做法,因为(除其他外)使代码更易于测试。所以我就是这么做的。

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

    ...
}

【讨论】:

    【解决方案2】:

    您可以将@Transactional 注解放在接口定义、接口上的方法、类定义或类上的公共方法之前。然而,仅仅存在@Transactional 注释并不足以激活事务行为。

    @Transactional 注释只是元数据,可以被一些运行时基础设施(@Transactional-aware)使用,并且可以使用元数据来配置具有事务行为的适当 bean。

    在前面的示例中,<tx:annotation-driven/> 元素开启了事务行为。

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

    你可以尝试的几点:

    1. 执行上述所有配置。
    2. 从 Controller 中移除 @Transactional 配置。
    3. 将 Service 对象与事务管理器 bean 一起引用。

    更多详情:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

    【讨论】:

      猜你喜欢
      • 2017-05-21
      • 2015-04-02
      • 1970-01-01
      • 1970-01-01
      • 2015-10-07
      • 2016-12-28
      • 1970-01-01
      • 2019-09-22
      • 2016-01-24
      相关资源
      最近更新 更多