Spring事务的传递性的使用

开发过程中遇到的问题

请添加图片描述

解决

Spring中配置隔离级别
在Spring项目中配置隔离级别只需要做如下操作

public int insertUser(User user){
    return userDao.insertUser(user);
}

上面的代码中我们使用了串行化的隔离级别来包住数据的一致性,这使它将阻塞其他的事务进行并发,所以它只能运用在那些低并发而又需要保证数据一致性的场景下。

隔离级别字典:

READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);

传播行为
在Spring中,当一个方法调用另外一个方法时,可以让事务采取不同的策略工作,如新建事务或者挂起当前事务等,这便是事务的传播行为。

定义
在Spring的事务机制中对数据库存在7种传播行为,通过枚举类Propagation定义。

    /**
     * 需要事务,默认传播性行为。
     * 如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
     */
    REQUIRED(0),
    /**
     * 支持事务,如果当前存在事务,就沿用当前事务,
     * 如果不存在,则继续采用无事务的方式运行子方法
     */
    SUPPORTS(1),
    /**
     * 必须使用事务,如果当前没有事务,抛出异常
     * 如果存在当前事务,就沿用当前事务
     */
    MANDATORY(2),
    /**
     * 无论当前事务是否存在,都会创建新事务允许方法
     * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
     */
    REQUIRES_NEW(3),
    /**
     * 不支持事务,当前存在事务时,将挂起事务,运行方法
     */
    NOT_SUPPORTED(4),
    /**
     * 不支持事务,如果当前方法存在事务,将抛出异常,否则继续使用无事务机制运行
     */
    NEVER(5),
    /**
     * 在当前方法调用子方法时,如果子方法发生异常
     * 只回滚子方法执行过的SQL,而不回滚当前方法的事务
     */
    NESTED(6);
	......
}

日常开发中基本只会使用到REQUIRED(0),REQUIRES_NEW(3),NESTED(6)三种。

NESTED和REQUIRES_NEW是有区别的。NESTED传播行为会沿用当前事务的隔离级别和锁等特性,而REQUIRES_NEW则可以拥有自己独立的隔离级别和锁等特性。

NESTED的实现主要依赖于数据库的保存点(SAVEPOINT)技术,SAVEPOINT记录了一个保存点,可以通过ROLLBACK TO SAVEPOINT来回滚到某个保存点。如果数据库支持保存点技术时就启用保存点技术;如果不支持就会新建一个事务去执行代码,也就相当于REQUIRES_NEW。

Transactional自调用失效
如果一个类中自身方法的调用,我们称之为自调用。如一个订单业务实现类OrderServiceImpl中有methodA方法调用了自身类的methodB方法就是自调用,如:

public void methodA(){
    for (int i = 0; i < 10; i++) {
        methodB();
    }
}
    
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
public int methodB(){
    ......
}

在上面方法中不管methodB如何设置隔离级别和传播行为都是不生效的。即自调用失效。

这主要是由于@Transactional的底层实现原理是基于AOP实现,而AOP的原理是动态代理,在自调用的过程中是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,于是就发生了自调用失败的现象。

要克服这个问题,有2种方法:

编写两个Service,用一个Service的methodA去调用另外一个Service的methodB方法,这样就是代理对象的调用,不会有问题;
在同一个Service中,methodA不直接调用methodB,而是先从Spring IOC容器中重新获取代理对象`OrderServiceImpl·,获取到后再去调用methodB。说起来有点乱,还是show you the code。

    private ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Transactional
    public void methodA(){
        OrderService orderService = applicationContext.getBean(OrderService.class);
        for (int i = 0; i < 10; i++) {
            orderService.methodB();
        }
    }

    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
    public int methodB(){
        ......
    }

}

上面代码中我们先实现了ApplicationContextAware接口,然后通过applicationContext.getBean()获取了OrderService的接口对象。这个时候获取到的是一个代理对象,也就能正常使用AOP的动态代理了。

按照这个方法写了, 但是最终遇到了下面的问题, 便没有再继续往下走了, 尝试对 进行了同步方法的重量级锁模式, 最终定位到可能是: Spring的事务提交了, 但是数据库的事务还没有提交;
最后另辟蹊径了.

请添加图片描述


关于在开发过程中, 使用默认隔离级别SUPPORTS, 子事务捕获异常,但是父事务依然 显示, 当前事务被标记为rollback的问题:

因为在开发的代码中只要存在 throw异常, 就会把当前事务标记为 rollback;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值