相信大家都是程序猿中的大牛,但是有些小细节总会被遗忘,但是呢,到了面试时又不得不拿出来记一下,然后应对面试,毕竟嘛,能成为面试官,我觉得都是待时间长业务熟悉透了升级,真正技术过硬,也没必要花时间来面试小喽啰了,对吧?所以大部分面试问的问题都差不多,Spring事务管理也恰好在里面了。
一、认识一下事务
事务这位老哥就是transaction,就是人们需要代码完成的一些增删改查,可以丢给这位老哥代替你来完成,不用你操心。Spring事务机制就是基于数据库那四个事务特性而设计开发出来的,所以我们可以先入手了解事务四大特性嘛,对吧。事务四大特性(ACID)分别为:Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
①原子性:原子性就是说一个事务里全部事情要么成功了,要么就失败了,不会在中间环节结束了。事务在执行期间如果发生了错误,就会回滚到事务发生前的状态,就好像事务从来没发生过似的。
②一致性:事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。举个例子就是:A和B各有5000元,A给B转账1000元,成功转账后,A+B的总额还是10000元。
③隔离性:每个事务执行都各自被隔离开,不相互影响各自的执行过程。也就是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
④持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
二、Spring事务的实现方式
Spring支持编程式事务管理以及声明式事务管理两种方式。
1、编程式事务管理
编程式事务管理是侵入性事务管理,编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。对于编程式事务管理,Spring推荐使用TransactionTemplate。
2、声明式事务管理
声明式事务管理属于spring事务管理的非侵入性事务管理的一种,建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。与编程式事务管理最大的区别就是,编程式事务管理是代码块实现的,不适用于业务量大的场景下,因为每个业务逻辑不同,会需要编写不同逻辑的事务管理代码,而声明式事务管理是属于方法级别的事务管理,只需要把事务逻辑配置在配置文件里,spring的会通过IOC容器反向控制把相关逻辑依赖注入应用到业务逻辑里。常用的方法有三种:①基于 TransactionProxyFactoryBean的声明式事务管理,②基于 @Transactional 的声明式事务管理,③基于Aspectj AOP配置事务。
①TransactionProxyFactoryBean的声明式事务管理
②基于 @Transactional 的声明式事务管理
可以看出,使用@Transactional注解的方式配置文件要简单的多,将事务交给事务注解驱动。它有个缺陷是他会把所有的连接点都作为切点将事务织入进去,显然只需要在buyStock()方法织入事务即可。下面看看最后一种实现,它就可以精准的织入到指定的连接点
知识点:
事务超时:@Transactional(timeout = 60)
如果用这个注解描述一个方法的话,线程已经跑到方法里面,如果已经过去60秒了还没跑完这个方法并且线程在这个方法中的后面还有涉及到对数据库的增删改查操作时会报事务超时错误(会回滚)。
如果已经过去60秒了还没跑完但是后面已经没有涉及到对数据库的增删改查操作,那么这时不会报事务超时错误(不会回滚)。
回滚:
Spring管理事务默认回滚的异常是什么?
答案是 RuntimeException或者Error。
注意:如果事务在try{}catch(Exception e){e.printStackTrace();}中跑,并且catch中只是打印e的话,那么事务不会rollback。因为异常被catch掉了,框架不知道发生了异常。
如果想要rollback,可以加上rollbackFor=Exception.class,然后:
(1)在方法上添加 throws Exception,将方法中出现的异常抛出给spring事务,
(2)去掉方法体中的try catch
(3)catch (Exception e) { throw e;}继续向上抛,目的是让spring事务捕获这个异常。
rollbackFor=Exception.class,catch(){
throw new RunTimeException();
}
如果不加rollbackFor=Exception.class,抛出newException() 是不会回滚的。Spring源码如下:
publicbooleanrollbackOn(Throwable ex) {
return(exinstanceofRuntimeException || exinstanceofError);
}
如果是RuntimeException或Error的话,就返回True,表示要回滚,否则返回False,表示不回滚。
只有spring事务捕获到Exception异常后,@Transactional(rollbackFor=Exception.class),才会起到应有的作用;catch (Exception e) { e.printStackTrace(); }这句是捕获try中出现的Exception然后将异常信息打印出来,仅仅是打印出来,然后什么也没干。
@Transactional(timeout = 60,rollbackFor=Exception.class)与rollbackFor=Exception.class的作用是
(1)让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)
(2)让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
checked Unchecked exception是运行时错误。
③基于Aspectj AOP配置事务
三、Spring事务传播机制
事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
常用的事务传播机制如下:
(1)PROPAGATION_REQUIRED
Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
(2)PROPAGATION_REQUES_NEW
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
(3)PROPAGATION_SUPPORT
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
(4)PROPAGATION_NOT_SUPPORT
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
(5)PROPAGATION_NEVER
该传播机制不支持外层事务,即如果外层有事务就抛出异常
(6)PROPAGATION_MANDATORY
与NEVER相反,如果外层没有事务,则抛出异常
(7)PROPAGATION_NESTED
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
四、事务的隔离级别
1、首先说明一下事务并发引起的三种情况:
(1) Dirty Reads 脏读
一个事务正在对数据进行更新操作,但是更新还未提交,另一个事务这时也来操作这组数据,并且读取了前一个事务还未提交的数据,而前一个事务如果操作失败进行了回滚,后一个事务读取的就是错误数据,这样就造成了脏读。
(2) Non-Repeatable Reads 不可重复读
一个事务多次读取同一数据,在该事务还未结束时,另一个事务也对该数据进行了操作,而且在第一个事务两次次读取之间,第二个事务对数据进行了更新,那么第一个事务前后两次读取到的数据是不同的,这样就造成了不可重复读。
(3) Phantom Reads 幻像读
第一个数据正在查询符合某一条件的数据,这时,另一个事务又插入了一条符合条件的数据,第一个事务在第二次查询符合同一条件的数据时,发现多了一条前一次查询时没有的数据,仿佛幻觉一样,这就是幻像读。
补充注意--非重复度和幻像读的区别:
非重复读是指同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。
幻像读是指同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。
表面上看,区别就在于非重复读能看见其他事务提交的修改和删除,而幻像能看见其他事务提交的插入。
2、隔离级别
(1)DEFAULT (默认)
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
(2)Read UnCommitted(读未提交)
这是事务最低级的隔离级别了,允许另外一个事务读到这个事务未提交的数据,会导致脏读、不可重复读和幻像读的问题。
(3)Read Committed(读已提交)
该级别是指只允许另外一个事务可以读到这个事务已提交的数据,避免了脏读问题,但是不可避免不可重复读和幻像读的问题。
(4)REPEATABLE_READ (可重复读)
确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
(5)SERIALIZABLE(串行化)
确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。
总结如下图: