通俗技术四Spring事务(Transaction)

Posted JAVA微讲堂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通俗技术四Spring事务(Transaction)相关的知识,希望对你有一定的参考价值。

往期回顾


                 

首先我们要牢记事务具有的四个属性:

原子性、一致性、隔离性、持久性这四个属性通常称为ACID特性。


原子性(atomicity)一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。


一致性(consistency)事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。


隔离性(isolation)一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。


持久性(durability)持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。



我们先来谈数据库的事物,也就是本地事务(local Transaction)


1)只有数据库引擎支持事务才能运用事务,比如mysql只有InnoDB引擎支持事务


2)如果表是InnoDB引擎,那么你执行SQL语句都会:开启事务、执行SQL语句、提交事务,发生异常回滚事务。这里的SQL语句可以是一条或多条,只要包含在一个事务中它们就是一个整体,也就是要么全部成功要么全部失败。数据库默认是自动开启事务,而在真正要用的事物的地方我们都会把自动改为手动控制事务。


3)我们不难看出我们谈论的事务,基石是数据库,而在JAVA的世界中JDBC是操作数据库的API,获得的第一个对象是数据库链接(Connection),事务再在Connection对象中获取。如果数据连接关闭,那么事务自然也销毁,因为事务属于数据库。


如果不接触任何第三方框架(如JTA,AOP)我们来思考一下我们对事务控制的代码


假如有一个业务要求,保存订单、修改库存,它们需要事务的支持。也就是说保存订单、修改库存是一个整体,要么全部成功要么全部失败。


办法1:在存储过程中开启一个事务,在这个事务中执行保存订单SQL和修改库存SQL,再用java代码调用该存储过程


办法2:DAO持久层,同一个方法中用JDBC获取数据库链接,用数据库链接获取事务,执行保存订单SQL和修改库存SQL。


办法3:DAO持久层写两个方法分别执行保存库存SQL和修改库存SQL.业务层(Service)调研这两个方法组成一个事务。那么我们就需要保持业务层(Service)获取的是同一个数据库链接,还需要保证在执行完业务层(Service)代码后数据库才能关闭。这样的代码你可以在脑海里想一下,侵入性非常严重。


在实际项目中我们后台经常会分为三层结构(不是必须)持久层(DAO)、业务层(Service)、控制层(Controller).持久层负责执行具体的SQL语句,业务层组装多个持久层方法组合成一个事务,控制层调用业务层,处理后的结果转发到客户端。所以我们事务应该放在业务层,也就是上面的办法3,Spring利用AOP优雅的、非侵入式的、简单的解决了这一难题。


Spring如何控制事务?如下图

 


1、我们调用目标方法时是通过代理调用,在Spring中就是面向切面编程AOP,也就是说Spring代理了我们的目标方法。


2、TranscationAdvisor对象通过事务管理(transactionManager)产生事务,CustomAdvisor对象创建事务通知(也无非就是开启事务、提交事务、回滚事务,当然在通知中还可以设置事务传播方式和隔离级别),既然事务管理可以获得事务,那么代表Spring在这一层(业务层)持有数据库链接。


3、CustomAdvisor对象把事务通知放到具体的目标方法上,这样该方法就具有了事务。执行的结果再一层一层的返回给调用者


在实际代码中我们只需要在需要事务的地方加上@Transactional 注解就搞定。而不去关心事务如何产生,如何放置,什么时候关闭。当然默认情况下是事务执行完后关闭数据库链接,如果涉及到懒加载的方法我们需要延迟关闭session(spring管理的数据库链接)。


在用SpringAOP管理事务我们还需要注意以下问题


1、在高并发情况下,数据可能出现:脏读、不可重复读、幻读


脏读: 对于两个事物 T1, T2, T1  读取了已经被 T2 更新但 还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.


不可重复读:对于两个事物 T1, T2, T1  读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.


幻读:对于两个事物 T1, T2, T1  从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.


其实这种情况和JAVA的生产者消费者案例一样,也无非就是用多个线程去操作一个资源,唯一的解决方案就是加锁。我们知道越是要安全也就是加锁范围越大,那么效率就越低(在计算机的世界没有绝对的好,有利就有弊我们只能找平衡)比如:解决脏读就是加事务级别的锁(mysql默认就加了事务级别的锁),解决不可重复读就加行级别的锁,解决幻读就加表级别的锁。


数据库用了一个叫事务隔离级别的方式来封装这些锁,代码只需要一句注解搞定@Transactional(isolation=Isolation.DEFAULT),isolation 就是事务隔离级别,所以数据库隔离级别是与数据库相关不同的数据库支持的隔离级别不同:

Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE

Mysql 支持 4 种事务隔离级别.如下图

 

通俗技术四Spring事务(Transaction)


2、假如我们把事务加到业务层(service层),那么一个方法就是一个独立的事务对吧?但是我们可能出现service层方法调用service 层方法的情况。比如:某个业务层有方法 A()和方法B(),它们都加了事务,如果方法B( )调用方法A(), B(){A()},根据一开始我们讲的事务的四个属性ACID,其中隔离性(isolation)是指一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。很显然上面的B()方法调用A()方法违背了这一原则,出现了事务调用事物,事务不再隔离。


Spring 为了弥补这一缺席创造了一个叫事务传播方式的技术。所以事务传播方式与数据库无关。Spring定义了以下7种事务传播方式,代码也只需一句注解@Transactional(propagation = Propagation.REQUIRED),propagation 就是定义事务的传播方式。

 

整个事务的代码我就不用贴上来了,我的目的是帮助大家理清思想。代码可以去翻阅我上课所写或是加我微信或是留言索取。


 


 

 


以上是关于通俗技术四Spring事务(Transaction)的主要内容,如果未能解决你的问题,请参考以下文章

聊一聊Spring中的 Transaction基本实现

Spring+Mybatis+Transaction事务控制最佳案例

Spring 事务Transaction源码深度解析

spring---transaction---事务的配置

Spring 事务Transaction源码深度解析

[mybatis-spring] Transaction 事务/事务处理/事务管理器