Spring事务失效原因与处理

Spring事务失效的原因主要有以下几种:
1.非public方法失效
@Transactional只有标注在public级别的方法上才能生效,对于非public方法将不会生效。这是由于Spring AOP不支持对private、protect方法进行拦截。声明 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,再由这个代理对象来统一管理。声明式事务原理是Spring事务会为@Transaction标注的方法的类生成AOP增强的动态代理类对象,并且在调用目标方法的拦截链中加入TransactionInterceptor进行环绕增加,实现事务管理。从原理上来说,动态代理是通过接口实现,所以自然不能支持private和protect方法的。而CGLIB是通过继承实现,其实是可以支持protect方法的拦截的,但Spring AOP中并不支持这样使用。如果需要对protect或private方法拦截则建议使用AspectJ。

2.自调用失效
当通过在同一个类的内部方法直接调用带有@Transactional的方法时,@Transactional将失效,例如:

public void saveAB(A a, B b)
{
    saveA(a);
    saveB(b);
}

@Transactional
public void saveA(A a)
{
    dao.saveA(a);
}

@Transactional
public void saveB(B b)
{
    dao.saveB(b);
}

在saveAB中调用saveA和saveB方法,两者的@Transactional都将失效。这是因为Spring事务的实现基于代理类,当在内部直接调用方法时,将不会经过代理对象,而是直接调用目标对象的方法,无法被TransactionInterceptor拦截处理。解决办法:
(1)ApplicationContextAware
通过ApplicationContextAware注入的上下文获得代理对象。

public void saveAB(A a, B b)
{
    Test self = (Test) applicationContext.getBean("Test");
    self.saveA(a);
    self.saveB(b);
}
**2)AopContext**
通过AopContext获得代理对象。
public void saveAB(A a, B b)
{
    Test self = (Test)AopContext.currentProxy();
    self.saveA(a);
    self.saveB(b);
}

(3)@Autowired
通过@Autowired注解注入代理对象。

@Component
public class Test {

    @Autowired
    Test self;

    public void saveAB(A a, B b)
    {
        self.saveA(a);
        self.saveB(b);
    }
    // ...
}

(4)拆分
将saveA、saveB方法拆分到另一个类中。

public void saveAB(A a, B b)
{
    txOperate.saveA(a);
    txOperate.saveB(b);
}

3. 不同类中调用方未开始事务
在不同类之间的方法调用中,如果 A 方法开启了事务,B 方法没有开启事务,B 方法调用了 A 方法。如果 B 方法中发生异常,但不是调用的 A 方法产生的,则异常不会使 A 方法的事务回滚,此时事务无效。如果 B 方法中发生异常,异常是调用的 A 方法产生的,则 A 方法的事务回滚,此时事务有效。在 B 方法上加上注解 @Trasactional,这样 A 和 B 方法就在同一个事务里了,不管异常产生在哪里,事务都是有效的。
简单地说,不同类之间方法调用时,异常发生在无事务的方法中,但不是被调用的方法产生的,被调用的方法的事务无效。只有异常发生在开启事务的方法内,事务才有效。

4. 数据库引擎选用不当
如果使用 MySQL 且引擎是 MyISAM,则事务会不起作用,原因是 MyISAM 不支持事务,改成 InnoDB 引擎则支持事务。

5.检查异常默认不回滚
在默认情况下,抛出非检查异常会触发回滚,而检查异常不会。
根据invokeWithinTransaction方法,我们可以知道异常处理逻辑在completeTransactionAfterThrowing方法中,其实现如下:
根据rollbackOn判断异常是否为回滚异常。只有RuntimeException和Error的实例,即非检查异常,或者在@Transaction中通过rollbackFor属性指定的回滚异常类型,才会回滚事务。否则将继续提交事务。所以如果需要对检查异常进行回滚,需要记得指定rollbackFor属性,不然将回滚失效。

检查异常是Exception的本身或者子类:
例如:IOException(输入输出异常)、FileNotFoundException(文件没发现异常)、SQLException(SQL异常)
非检查异常是RuntimeException的本身或子类:
例如:算数异常(ArithmeticException)、空指针异常(NullPointerException),数组越界异常(ArrayIndexOutOfBoundException)

6.catch异常无法回滚
我们说只有抛出非检查异常或是rollbackFor中指定的异常才能触发回滚。如果我们把异常catch住,而且没抛出,则会导致无法触发回滚,这也是开发中常犯的错误。例如:

@Transactional
public void insert(List<User> users) {
    try {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        for (User user : users) {
            String insertUserSql = "insert into User (id, name) values (?,?)";
            jdbcTemplate.update(insertUserSql, new Object[] { user.getId(),
                                                             user.getName() });
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

这里由于catch住了所有Exception,并且没抛出。当插入发生异常时,将不会触发回滚。
但同时我们也可以利用这种机制,用try-catch包裹不用参与事务的数据操作,例如对于写入一些不重要的日志,我们可将其用try-catch包裹,避免抛出异常,则能避免写日志失败而影响事务的提交。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值