在spring中实现事务有多种方式,主要是两种:一种是声明式事务,一种是编程式事务,今天我们就讲声明式事务中的一种,使用注解@Transactional,这个注解的作用就是帮助我们在代码执行完毕之后自动提交事务,在发生异常时进行回滚。
比如以下这段代码:
@Transactional public void updateDBInfo(List<Entity> entityList){ //待更新的数据 entityList.stream().foreach((arg) -> { arg.setModifier("xxx"); arg.setModifierTime("xxxx-xx-xx hh:mm:ss"); }); this.updateBatchById(entityList); }
这是一段非常简单的代码段。假如我们就是需要加进行更新,但是如果这段代码需要涉及到锁的话,如果架构是分布式,也就是部署了多台service Application服务器,然后进行负债均衡,那么此时如果有多个用户同时操作的话,每个用户的请求会由于负债均衡可能到达不同的service Application服务器应用。所以这时候我们的锁用线程级别的锁是不行的,只能用进程级别的锁,也就是分布式锁。分布式锁的话可以用redis的那个命令叫setNx,这里我就不过多赘述了。主要讲在事务里面有锁会造成什么问题,以及如何解决该问题。
如果我们还是使用这个注解@Transactional,这个注解我们刚刚也说了。这个注解的作用就是帮助我们在代码执行完毕之后自动提交事务,在发生异常时进行回滚。
首先改造代码进行加锁:
@Transactional public void updateDBInfo(List<Entity> entityList){ RLock lock = null; try { lock = redissonTool.tryLock(RedisKeyConstants.REDIS_KEY_PURCHASE_ORDER_EXTEND, Codes.KEY_ERROR); //待更新的数据 entityList.stream().foreach((arg) -> { arg.setModifier("xxx"); arg.setModifierTime("xxxx-xx-xx hh:mm:ss"); }); this.updateBatchById(entityList); } finally { redissonTool.unLock(lock); } }
如果我们是这样写的话,那么会有什么问题呢?
当代码执行完毕,也就是finally里面的锁都释放了,去提交事务,但是提交事务也是需要时间的,有可能还没提交事务完,由于锁释放了,其他客户端就会拿到锁了,但是此时该用户感知不到此时DB数据库中的数据是最新的,也就是所谓的可见性,什么是可见性?
可见性:顾名思义就是一个线程的操作结果对其他所有线程都是可见的,因为每一个线程都共享同一个共享主内存。
那么这时候另一个用户在客户端就不知道这个DB的数据 是更改的,又会去更改,然后提交事务,这时候刚刚那个客户端的用户也提交事务成功了,那么就会造成脏数据了,数据不一致。
我给大家画个图展示:
但是由于我刚刚说的这种,A还没提交更新事务之后,锁释放,B拿到,B更新完,提交更新事务,成功改为2,A的提交更新事务完成。此时就还是1.就是脏数据了。
所以有什么办法解决呢?
那我们把自动事务注解@Transactional改成手动事务。让事务提交成功之后再去释放锁,而不是在锁释放之后再提交事务。
手动事务我用的是transactionTemplate.execute(TransactionCallback callback);然后用的是TransactionCallback接口的抽象实现类,用匿名内部类。
TransactionCallbackWithoutResult的抽象方法:doInTransactionWithoutResult
也就是:
transactionTemplate.execute( new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //业务更新逻辑 } });
总结:
在事务中碰到锁,必须使用手动事务,让事务提交成功之后,再去释放锁。而非释放锁之后再提交事务。也就是不能使用@Transactional注解的自动提交事务。
一般模板:
RLock rLock = null; try{ rLock = redissonTool.tryLock("REDIS_LOCK_NAME"); }catch(){ transactionTemplate.execute(new TransactionCallbackWithoutResult(){ protected void doInTransctionWithoutResult(){ //业务更新逻辑; } }) }finally{ rlock.unlock(); }
最后:
如果大家觉得这篇文章对你们有所帮助的话,麻烦点个免费的赞赞,谢谢,也祝各位码农在未来的IT道路上越走越远。