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

文章详细介绍了Spring事务可能出现的失效情况,包括final或static修饰的事务方法、非public访问权限、同一类内部调用、事务注解配置错误、事务被覆盖以及嵌套事务和异常处理不当导致的问题。这些问题都与Spring的AOP机制及事务管理密切相关。
摘要由CSDN通过智能技术生成

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

失效原因

  1. 事务方法被final、static关键字修饰:这是因为Spring事务的实现依赖于AOP技术,而final、static方法无法被代理,因此在这些方法中调用事务方法,事务无法生效。
  2. 方法访问权限不是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:如果当前存在事务,则在嵌套事务内执行;否则,创建一个新的事务。这种方式也是嵌套事务的实现方式。
    在嵌套事务中,如果外部事务回滚了,那么内部事务也会回滚;如果内部事务回滚了,只会回滚内部事务,而不会影响到外部事务。

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

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

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

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

@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 方法的事务并不会回滚,导致了事务失效。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值