> 文章列表 > 遇到Spring事务失效,你该怎么办?

遇到Spring事务失效,你该怎么办?

遇到Spring事务失效,你该怎么办?

Spring 事务场景失效是一个常见的问题。今天来分析这个问题。

在这里插入图片描述

1、事务方法被final、static关键字修饰,方法访问权限不是public

@Service
public class UserService {@Autowiredprivate UserDao userDao;// final修饰的事务方法@Transactionalpublic final void addUser(User user) {userDao.addUser(user);}// 访问权限不是public的事务方法@Transactionalprotected void updateUser(User user) {userDao.updateUser(user);}// 静态方法的事务方法@Transactionalpublic static void deleteUser(int userId) {userDao.deleteUser(userId);}
}

失效原因

  1. 事务方法被final、static关键字修饰:这是因为Spring事务的实现依赖于AOP技术,而final、static方法无法被代理,因此在这些方法中调用事务方法,事务无法生效。
  2. 方法访问权限不是public:Spring事务的实现也是基于AOP的,所以在非public的方法中调用事务方法,无法触发AOP代理,因此事务不会生效。

2、同一个类中,方法内部调用

@Service
public class UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void addUser(User user) {// 事务方法内部调用了updateUser方法,事务失效updateUser(user);}public void updateUser(User user) {userDao.updateUser(user);}
}

失效原因
在同一个类中,事务方法内部调用其他方法时,可能会导致事务失效。这是因为Spring的事务是基于AOP(面向切面编程)实现的,在同一个类中的方法内部调用其他方法时,实际上是调用的类的内部方法,而不是通过代理调用方法,从而导致事务无法生效。

3、事务注解配置错误

  • 没有开启事务注解扫描或者没有在配置文件中开启事务。
<!-- 开启事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" />
</bean><!-- 开启事务注解扫描 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 没有在需要开启事务的方法上添加@Transactional注解。
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void addUser(User user) {userDao.addUser(user);}
}
  • 在事务方法上添加了错误的propagation或isolation属性值。
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void transferMoney(String fromAccount, String toAccount, double amount) {// transfer money from one account to another
}
  • 在注解中配置了错误的rollbackFor或noRollbackFor属性值。
@Transactional(rollbackFor = RuntimeException.class, noRollbackFor = BusinessException.class)
public void updateUserInfo(User user) throws BusinessException {// update user information
}

4、 事务注解被覆盖导致事务失效

@Transactional(propagation = Propagation.REQUIRED)
public class ParentClass {public void doSomething() {// ...}
}public class ChildClass extends ParentClass {@Transactional(propagation = Propagation.NOT_SUPPORTED)public void doSomething() {// ...}
}

在上述代码中,ParentClass 中的事务注解 @Transactional(propagation = Propagation.REQUIRED) 覆盖了 ChildClass 中的事务注解 @Transactional(propagation = Propagation.NOT_SUPPORTED),因此当调用 ChildClass 中的 doSomething 方法时,事务将会失效。为了解决这个问题,可以在子类中将事务注解的属性值与父类保持一致。

5、 嵌套事务和异常被捕获导致事务失效

嵌套事务是指在一个事务内部,开启了一个新的事务,这个新事务与外部事务是嵌套关系,也就是内部事务依赖于外部事务,只有外部事务提交成功,内部事务才能生效。

在 Spring 中,通过设置事务传播级别来实现嵌套事务,常见的传播级别包括:

  • REQUIRED:如果当前存在事务,则加入该事务;否则,创建一个新的事务。这是默认值。
  • REQUIRES_NEW:每次都创建一个新的事务,并将当前事务挂起。
  • NESTED:如果当前存在事务,则在嵌套事务内执行;否则,创建一个新的事务。这种方式也是嵌套事务的实现方式。
    在嵌套事务中,如果外部事务回滚了,那么内部事务也会回滚;如果内部事务回滚了,只会回滚内部事务,而不会影响到外部事务。

然而,使用嵌套事务需要注意一些坑:

  1. 数据库支持
    嵌套事务是由数据库来实现的,不同的数据库对于嵌套事务的支持不同,有些数据库甚至不支持嵌套事务,例如 MySQL 默认是不支持嵌套事务的。

  2. 事务管理器
    Spring 事务是通过事务管理器来实现的,不同的事务管理器对于嵌套事务的支持也不同,如果使用的事务管理器不支持嵌套事务,那么嵌套事务就会失效。

  3. 异常处理
    在嵌套事务中,如果内部事务抛出了异常,并且外部事务没有捕获这个异常,那么整个事务会回滚,包括内部事务和外部事务。因此,在嵌套事务中,要注意异常处理。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactional(propagation = Propagation.REQUIRED)public void updateUser(User user) {jdbcTemplate.update("update user set name = ? where id = ?", user.getName(), user.getId());try {insertLog(user.getId());} catch (Exception e) {e.printStackTrace();}}@Transactional(propagation = Propagation.NESTED)public void insertLog(Long userId) {jdbcTemplate.update("insert into log(user_id) values(?)", userId);throw new RuntimeException("插入日志失败");}}

上述代码中,updateUser 方法中调用了 insertLog 方法,insertLog 方法使用了 Propagation.NESTED 的传播级别来实现嵌套事务。当插入日志时,会抛出 RuntimeException 异常,并被 updateUser 方法中的 try-catch 块捕获。在这种情况下,虽然 insertLog 方法的事务会回滚,但是由于使用的是嵌套事务,所以 updateUser 方法的事务并不会回滚,导致了事务失效。