从阿里规约看Spring事务
目标:事务失效引发的灾难
如下图(张三--->李四转账)
tips
下订单-------订单支付-----减库存(失败)
超卖现象
代码回忆:
//实现类publicclassUserServiceImplimplementsUserService{@AutowiredprivateUserMapperuserMapper;@ResourceprivateLogServicelogService;@Override@Transactional// @Transactional(rollbackFor = Exception.class)publicvoidinsert()throwsException{method1_Test();}//模拟转账@Transactionalprivatevoidmethod1_Test()throwsException{System.out.println(">>>>>>>>>>>进入到业务方法");Useruser=newUser();user.setName("张三");userMapper.insertUser(user);//张三扣减500元addPayment();//模拟李四增加500元(检查异常)}//FileNotFoundException extends IOExceptionprivatevoidaddPayment()throwsFileNotFoundException{FileInputStreamin=newFileInputStream("a.txt");//模拟检查异常}}......略如果说你从从事务方法中抛出的是检查异常(io、sql),那么这个时候,Spring将不能进行事务回滚。
是不是很恐怖呢??
所以说,阿里规定1、让检查异常也回滚:你就需要在整个方法前加上@Transactional(rollbackFor=Exception.class)
2、让非检查异常不回滚:需要加入@Transactional(notRollbackFor=RunTimeException.class)
3、不需要事务管理(or 日志丢失)需要加入@Transactional(propagation=Propagation.NOT_SUPPORTED)
课程目标总结
1、解决事务失效:通过源码学习如何让检查异常也回滚(or 运行异常不回滚);从源码角度深入底层原理
2、解决无需事务控制;查询 or 日志记录;通过传播属性如何控制;底层是如何实现的
3、正常的事务执行流程在源码中是如何实现的
1.1 Spring事务总体介绍
在Spring中,事务有两种实现方式:
编程式事务管理:编程式事务管理使用TransactionTemplate可实现更细粒度的事务控制。
申明式事务管理:基于Spring AOP实现。
其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务好处:
申明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单,且大部分业务都可以满足,推荐使用。
管是编程式事务还是申明式事务,最终调用的底层核心代码是一致的
1.1.1 编程式事务实现方式
编程式事务,Spring已经给我们提供好了模板类TransactionTemplate,可以很方便的使用,如下图
TransactionTemplate全路径名是:org.springframework.transaction.support.TransactionTemplate。这是spring对事务的模板类
实现的接口
用来执行事务的回调方法,
publicinterfaceTransactionOperations{@Nullable<T>T execute(TransactionCallback<T>action)throwsTransactionException;}InitializingBean这个是典型的spring bean初始化流程中 ,用来在bean属性加载完毕时执行的方法。
publicinterfaceInitializingBean{voidafterPropertiesSet()throwsException;}TransactionTemplate的2个接口的impl方法做了什么?
afterPropertiesSet如下
// 只是校验了事务管理器不为空@OverridepublicvoidafterPropertiesSet(){if(this.transactionManager==null){thrownewIllegalArgumentException("Property transactionManager is required");}}execute方法如下
public<T>T execute(TransactionCallback<T>action)throwsTransactionException{Assert.state(this.transactionManager!=null,"No PlatformTransactionManager set");if(this.transactionManagerinstanceofCallbackPreferringPlatformTransactionManager){return((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this,action);}else{//TODO 创建事务 (与声明事务调用同一个方法)TransactionStatusstatus=this.transactionManager.getTransaction(this);T result;try{// 2.执行业务逻辑,这里就是用户自定义的业务代码。如果是没有返回值的,就是doInTransactionWithoutResult()。result=action.doInTransaction(status);}catch(RuntimeException|Errorex){// Transactional code threw application exception -> rollback// 应用运行时异常/错误异常 -> 回滚,调用AbstractPlatformTransactionManager的rollback(),事务提交回滚//TODO 回滚((与声明事务调用同一个方法)rollbackOnException(status,ex);throwex;}catch(Throwableex){// 未知异常 -> 回滚,调用AbstractPlatformTransactionManager的rollback(),事务提交回滚// Transactional code threw unexpected exception -> rollbackrollbackOnException(status,ex);thrownewUndeclaredThrowableException(ex,"TransactionCallback threw undeclared checked exception");}// TODO 事务提交 (与声明事务调用同一个方法)this.transactionManager.commit(status);returnresult;}}总结
事务模板TransactionTemplateI里面的execute方法【创建事务】【提交事务】【回滚事务】和声明式事务调用的都是同一个底层方法
1.1.2 声明式事务实现方式
声明式事务的用法
@Transactional注解可以加在类或方法上
1、类:在类上时是对该类的所有public方法开启事务。
2、方法:加在方法上时也是只对public方法起作用。
注意
@Transactional注解也可以加在接口上,但只有在设置了基于接口的代理时才会生效,因为注解不能继承。所以该注解最好是加在类的实现上。
1.2 不容忽视的异常体系
目标:Java异常体系(面试常问)与Spring事务存在什么联系
结论:
Spring事务默认只回滚运行时异常和Error
伪代码如下
@Transactionpublicvoidinsert(){m_insert();in_insert();//只有运行时异常 & Error 才可以回滚}Thorwable类是所有异常和错误的超类,有两个子类Error和Exception,分别表示错误和异常。
异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。
1、Error与Exception
Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
2、运行时异常和非运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
1.3 事务传播行为与隔离级别
1.3.1 事务传播行为
什么是事务传播
事务传播用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
tips
两大类【有事务的情况】【没事务的情况】
代码理解概念
@Transactionalprivatevoidmethod5_Test()throwsException{System.out.println(">>>>>>>>>>>进入到业务方法");Useruser=newUser();user.setName("张三");userMapper.insertUser(user);logService.insert(getLogEntity());thrownewRuntimeException();}.............LogService.java@Override// @Transactional(propagation = Propagation.NOT_SUPPORTED)@Transactionalpublicvoidinsert(Loglog)throwsException{System.out.println(">>>>>>>>>>>进入到日志方法");// Log log = new Log();// log.setName("业务日志记录");logMapper.insertLog(log);}1.3.2 事务隔离级别
什么是事务的隔离
5大类
隔离性是指多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔
隔离级别说明TransactionDefinition.ISOLATION_DEFAULT(默认)PlatformTransactionManager的默认隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。TransactionDefinition.ISOLATION_READ_UNCOMMITTED(读未提交)这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读TransactionDefinition.ISOLATION_READ_COMMITTED(读已提交)保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。TransactionDefinition.ISOLATION_REPEATABLE_READ(可重复读)这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读TransactionDefinition. ISOLATION_SERIALIZABLE(串行化)代价最大、可靠性最高的隔离级别,所有的事务都是按顺序一个接一个地执行如果不考虑隔离性,会发生什么事呢?
脏读:
脏读是指一个事务在处理数据的过程中,读取到另一个为提交事务的数据
不可重复读:
不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了。
幻读
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读
1.读未提交(Read uncommitted):
这种事务隔离级别下,select语句不加锁。
此时,可能读取到不一致的数据,即“读脏”。这是并发最高,一致性最差的隔离级别。
2.读已提交(Read committed):
可避免脏读的发生。
在互联网大数据量,高并发量的场景下,几乎不会使用上述两种隔离级别。
3.可重复读(Repeatable read):
MySql默认隔离级别。
可避免脏读、不可重复读的发生。
4.串行化(Serializable ):
可避免脏读、不可重复读、幻读的发生
1.4 Spring事务源码深度剖析
1.4.1 Spring事务环境介绍
目标:事务测试环境介绍
1.4.2 事务是何时被织入的
目标:事务在Spring哪个阶段织入的
思考:
程序在运行的时候【userService】为什么是代理对象
需要解决的问题:
1、代理对象是如何生成的
2、代理对象如何调用到了invoke方法
1.4.3 事务源码入口在哪里
1)事务三大接口介绍目标:了解非常核心的事务三大接口
Spring事务三大接口介绍
1、PlatformTransactionManager: (平台)事务管理器接口
PlatformTransactionManager 接口是 Spring 提供的平台事务管理器顶级接口,用于管理事务。
Spring并不直接管理事务,而是提供了多种事务管理器;他们将事务管理的职责委托给Hibernate或者JTA等持久化机制的事务框架来实现
通过这个接口,Spring为各个平台,比如JDBC等都提供了对应的事务管理器;但是具体实现就是下游事务框架了
publicinterfacePlatformTransactionManager{TransactionStatusgetTransaction(TransactionDefinitiondefinition):用于获取事务状态信息。voidcommit(TransactionStatusstatus):用于提交事务。voidrollback(TransactionStatusstatus):用于回滚事务。}该接口中提供了三个事务操作方法,具体如下。
TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。void commit(TransactionStatus status):用于提交事务。void rollback(TransactionStatus status):用于回滚事务。下面是 PlatformTransactionManager各种实现
2、TransactionDefinition:事务定义信息接口
比如:事务传播行为、隔离级别、超时、只读、回滚规则)
//7大传播+5大隔离+超时、只读、回滚publicinterfaceTransactionDefinition{intPROPAGATION_REQUIRED=0;//默认:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务.....略}3、TransactionStatus:事务运行状态接口
publicinterfaceTransactionStatusextendsSavepointManager,Flushable{booleanisNewTransaction();//获取是否是新事务....略}TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作
名称说明void flush()刷新事务boolean hasSavepoint()获取是否存在保存点boolean isCompleted()获取事务是否完成boolean isNewTransaction()获取是否是新事务boolean isRollbackOnly()获取是否回滚void setRollbackOnly()设置事务回滚2)事务源码调用入口目标:找到Spring事务的调用入口
tips
入口拦截器org.springframework.transaction.interceptor.TransactionInterceptor#invoke
TransactionInterceptor源码如下
// 获取目标类@Override@NullablepublicObjectinvoke(finalMethodInvocationinvocation)throwsThrowable{// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.ClasstargetClass=(invocation.getThis()!=null?AopUtils.getTargetClass(invocation.getThis()):null);// 开始调用父类方法returninvokeWithinTransaction(invocation.getMethod(),targetClass,invocation::proceed);}Transactional 注解
@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic@interfaceTransactional{@AliasFor("transactionManager")Stringvalue()default"";@AliasFor("value")StringtransactionManager()default"";// 传播行为// 一个开启了事务的方法A,调用了另一个开启了事务的方法B,此时会出现什么情况?这就要看传播行为的设置了Propagationpropagation()defaultPropagation.REQUIRED;//isolation属性是用来设置事务的隔离级别,数据库有四种隔离级别://读未提交、读已提交、可重复读、可串行化。MySQL的默认隔离级别是可重复读Isolationisolation()defaultIsolation.DEFAULT;//timtout是用来设置事务的超时时间,可以看到默认为-1,不会超时。inttimeout()defaultTransactionDefinition.TIMEOUT_DEFAULT;// readOnly属性用来设置该属性是否是只读事务,只读事务要从两方面来理解:// 它的功能是设置了只读事务后在整个事务的过程中,其他事务提交的内容对当前事务是不可见的// 只读事务中只能有读操作,不能含有写操作,否则会报错booleanreadOnly()defaultfalse;//当方法内抛出指定的异常时,进行事务回滚。默认情况下只对RuntimeException回滚。Class