@Transactional 事务加了 锁 为什么还有并发问题?

19 篇文章 11 订阅
3 篇文章 0 订阅

一、原因分析

Spring 中通过在方法上添加注解 @Transactional 可以很好的处理事务问题。Spring对此的处理原理是对 加了 @Transactional 注解的方法 添加 AOP切面来时先事务管理的。
synchronized 最大范围也就是方法级别的。 事务和synchronized 关系如下所示
在这里插入图片描述
由上图可以看出,当线程1 释放了锁,还未提交事务之前,线程2 已经获取锁并提前提交了事务,从而导致了并发的问题。

二、解决方法

1、方法一 增强事务隔离级别

可以把事务的隔离级别设置为 SERIALIZABLE 不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。

 @Transactional(isolation = Isolation.SERIALIZABLE)
    public synchronized void update(Long id, Long seq){
        Test1Entity entity = test1Mapper.selectById(id);
        test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));
    }

2、方法二 提升加锁位置

如果业务逻辑非常简单,对高并发行要求不高的话,可以把 锁操作添加到 控制层,如下所示:

 // Controller
   @PutMapping("/update/{id}/{seq}")
    public Test1Entity update(@PathVariable("id") Long id,
                             @PathVariable("seq") Long seq){
        // 添加锁操作。 
        synchronized (Class.class){ // 此处为了示例,要根据业务合理加锁
            testService.update(id, seq);
        }
        return new Test1Entity(id, seq);
    }

 // Service
@Service
public class TestServiceImpl {
    @Resource
    private Test1Mapper test1Mapper;
    
    @Transactional
    public void update(Long id, Long seq){
        Test1Entity entity = test1Mapper.selectById(id);
        test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));
    }
}

3、方法三 抽离事务代码

方法一和方法二的效率都比较低,另一种方式可以把,可以把需要 把需要并发控制的业务,单独抽离出来,进行事务控制操作。如下所示:


public void complex(Long id, Long seq){
        // 其他业务处理
        lock.lock();
        try {
            service.update(id, seq);
        } finally {
           lock.unlock();
        }
        // 其他业务处理
    }

    @Transactional
    public void update(Long id, Long seq){
        Test1Entity entity = test1Mapper.selectById(id);
        test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));
    }

4、方法四 手动开启事务

方法三中可能会增加一个类的编写,也可以在同一个方法中通过手动开启事务的方式实现。如下所示:


@Service
public class TestServiceImpl {

    @Resource
    private Test1Mapper test1Mapper;

    private static final Lock lock = new ReentrantLock();
    
    @Resource
    private  DataSourceTransactionManager transactionManager;
    @Resource
    TransactionDefinition transactionDefinition;

    public void update(Long id, Long seq){
        // 其他业务处理
        lock.lock();
        // 开启事务
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);;
        try {
            Test1Entity entity = test1Mapper.selectById(id);
            test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));

            // 提交事务
            transactionManager.commit(transaction);
        }catch (Exception e){
            // 回滚事务
            transactionManager.rollback(transaction);
        }finally {
           lock.unlock();
        }
        // 其他业务处理
    }

}

这个手动开启事务,需要每个方法都需要实现,这个也是比较繁琐,这种方式可以抽象出一个公共类,统一来实现事务的处理。 可以自己脑补

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值