Spring 事务场景失效是一个常见的问题。今天来分析这个问题。
1、事务方法被final、static关键字修饰,方法访问权限不是public
@Service
public class UserService {
@Autowired
private UserDao userDao;
// final修饰的事务方法
@Transactional
public final void addUser(User user) {
userDao.addUser(user);
}
// 访问权限不是public的事务方法
@Transactional
protected void updateUser(User user) {
userDao.updateUser(user);
}
// 静态方法的事务方法
@Transactional
public static void deleteUser(int userId) {
userDao.deleteUser(userId);
}
}
失效原因
- 事务方法被final、static关键字修饰:这是因为Spring事务的实现依赖于AOP技术,而final、static方法无法被代理,因此在这些方法中调用事务方法,事务无法生效。
- 方法访问权限不是public:Spring事务的实现也是基于AOP的,所以在非public的方法中调用事务方法,无法触发AOP代理,因此事务不会生效。
2、同一个类中,方法内部调用
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public 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 {
@Autowired
private UserDao userDao;
@Transactional
public 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:如果当前存在事务,则在嵌套事务内执行;否则,创建一个新的事务。这种方式也是嵌套事务的实现方式。
在嵌套事务中,如果外部事务回滚了,那么内部事务也会回滚;如果内部事务回滚了,只会回滚内部事务,而不会影响到外部事务。
然而,使用嵌套事务需要注意一些坑:
-
数据库支持
嵌套事务是由数据库来实现的,不同的数据库对于嵌套事务的支持不同,有些数据库甚至不支持嵌套事务,例如 MySQL 默认是不支持嵌套事务的。 -
事务管理器
Spring 事务是通过事务管理器来实现的,不同的事务管理器对于嵌套事务的支持也不同,如果使用的事务管理器不支持嵌套事务,那么嵌套事务就会失效。 -
异常处理
在嵌套事务中,如果内部事务抛出了异常,并且外部事务没有捕获这个异常,那么整个事务会回滚,包括内部事务和外部事务。因此,在嵌套事务中,要注意异常处理。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private 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 方法的事务并不会回滚,导致了事务失效。