有时候,我们明明在类或者方法上添加了@Transactional注解,却发现方法并没有按事务处理。其实,以下场景会导致Spring的@Transactional事务失效。
1、事务方法所在的类没有加载到Spring IOC容器中。
@Transactional是Spring的注解,未被Spring管理的类中的方法不受@Transactional注解控制,这个应该很好理解。
2、方法没有被public修饰。
众所周知,java的访问权限修饰符有:private、default、protected、public四种,但是@Transactional注解只能作用于public修饰的方法上。之所以会失效是因为在Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。- protected TransactionAttribute computeTransactionAttribute(Method method,
- Class<?> targetClass) {
- // Don't allow no-public methods as required.
- if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
- return null;
- }
复制代码 此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
3、在同一个类中的方法调用。
假如在同一个类中有A、B两个方法,如下:- @Service
- public class UserServiceImpl {
- @Autowired
- UserMapper userMapper;
- public void A() {
- B();
- }
- @Transactional
- public void B() {
- userMapper.deleteById(1);
- int i = 10 / 0; //模拟发生异常
- }
-
- }
复制代码 像上面的代码,B方法使用@Transactional注解标注,在A方法中调用了B方法,在外部调用A方法时,B方法的事务不会生效。这是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
那么如果确实在同一类中调用事务方法怎么办呢?有以下3种方法解决:
- @Service
- public class UserServiceImpl {
- @Autowired
- UserMapper userMapper;
- @Autowired
- UserServiceImpl userServiceImpl;
- public void A() {
- userServiceImpl.B();
- }
- @Transactional
- public void B() {
- userMapper.deleteById(1);
- int i = 10 / 0; //模拟发生异常
- }
- }
复制代码
- 通过 ApplicationContext 引入bean
- @Service
- public class UserServiceImpl {
- @Autowired
- UserMapper userMapper;
- @Autowired
- ApplicationContext applicationContext;
- public void A() {
- ((UserServiceImpl) applicationContext.getBean("userServiceImpl")).B();
- }
- @Transactional
- public void B() {
- userMapper.deleteById(1);
- int i = 10 / 0; //模拟发生异常
- }
- }
复制代码 在启动类上添加注解@EnableAspectJAutoProxy(exposeProxy = true),表示是否对外暴露代理对象,即是否可以获取AopContext。- @Service
- public class UserServiceImpl {
- @Autowired
- UserMapper userMapper;
- public void A() {
- ((UserServiceImpl) AopContext.currentProxy()).B();
- }
- @Transactional
- public void B() {
- userMapper.deleteById(1);
- int i = 10 / 0; //模拟发生异常
- }
- }
复制代码 4、方法的事务传播类型不支持事务。
若propagation属性设置如下三种事务传播行为,事务将不会发生回滚。
1、SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
2、NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
3、NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
Spring事务传播行为可以查阅我之前的一篇文章:Spring事务(一)-事务传播行为
5、不正确地捕获异常。
使用了try-catch代码块将异常捕捉了,没有向上抛出异常,事务不会回滚。- @Transactional
- private void A() throws Exception {
- @Autowired
- UserMapper userMapper;
-
- try {
- //A方法插入数据
- User u = New User();
- u.setId(1);
- u.setName("张三");
- userMapper.insert(u);
- /**
- * B方法也插入数据
- */
- b.insert();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
复制代码 上面的代码,如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那么,事务并不会回滚,而且会抛出如下异常:- org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
复制代码 因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。
6、属性rollbackFor 设置错误。
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 属性。
7、数据库不支持事务。
以MySQL为例,InnoDB引擎是支持事务的,而像MyISAM、MEMORY等是不支持事务的。 从MySQL5.5.5开始默认的存储引擎是InnoDB,之前默认都是MyISAM。
8、方法使用final修饰。
如果一个方法不想被子类重写,那么我们就可以把他写成final修饰的方法。如果事务方法使用final修饰,那么AOP就无法在代理类中重写该方法,事务就不会生效。同样的,static修饰的方法也无法通过代理变成事务方法。
9、未开启事务。
如果是SpringBoot项目,那么SpringBoot通过DataSourceTransactionManagerAutoConfiguration自动配置类帮我们开启了事务。如果是传统的Spring项目,则需要我们自己配置。在Spring配置文件配置如下:- <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
- <property name="entityManagerFactory" ref="entityManagerFactory"/>
- </bean>
- <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <tx:method name="insert*" propagation="REQUIRED"/>
-
- <tx:method name="update*" propagation="REQUIRED"/>
- <tx:method name="delete*" propagation="REQUIRED"/>
- </tx:attributes>
- </tx:advice>
- <aop:config proxy-target-class="true" expose-proxy="true">
-
- <aop:pointcut id="txPointcut"
- expression="execution( * com.posun..service.impl..*+.*(..)) || execution(* com.posun..task..*+.*(..))
- || execution(* com.posun.report.ReportJdbc.*(..)) "/>
- <aop:advisor id="txAdvisor" advice-ref="txAdvice" pointcut-ref="txPointcut"/>
- </aop:config>
复制代码 或者,使用注解的方式。- [/code][size=4][b] 10、多线程调用[/b][/size]
- [code]@Service
- public class UserServiceImpl {
- @Autowired
- UserMapper userMapper;
- @Transactional
- public void A() {
- userMapper.deleteById(1);
- new Thread(()->{
- userMapper.deleteById(2);
- int i = 10/0; //模拟发生异常
- }).start();
- }
- }
复制代码 以上代码,A方法中,启动了一个新的线程,并在新的线程中发生了异常,这样A方法是不会发生回滚的。因为两个操作不在一个线程中,获取到的数据库连接不一样,从而是两个不同的事务,所以也不会回滚。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |