趣文网 > 作文大全

这可能是对 Spring 事务原理讲解最透彻的文章了

2020-11-25 06:00:01
相关推荐

Spring事务的基本原理

首先澄清一个误点:Spring本身是没有事务一说的,数据库对事务的支持才是Spring事务的本质。

假设我们经常使用的MySQL数据库不提供事务功能,毫无疑问Spring也就无法提供事务功能了。纯粹使用jdbc来操作数据库,必须通过以下步骤才能使用到数据库的事务,步骤如下:

获取连接 Connection con = DriverManager.getConnection()开启事务con.setAutoCommit(true/false);执行CRUD提交事务/回滚事务 con.commit() / con.rollback();关闭连接 conn.close();Spring的事务管理会自动帮我们完成上面的2和4的步骤,不再需要我们自己去手动开启和关闭。这里就可以提出一个问题:Spring是如何再CRUD的前后开启和关闭事务的呢?要回答这个问题就必须中整体上去了解Spring的事务管理实现原理了。

下面我以注解方式来向大家做介绍:

首先再配置文件中开启事务的注解驱动,在需要使用到事务的类和方法上使用@Transactional标识。Spring 在启动的时候会去解析生成相关的bean,这个过程中会检测拥有相关注解的类和方法,同时给带有该注解的类和方法生成相应的代理,Spring会根据在注解@Transaction上的相关参数设置进行配置注入。Spring就是在生成的代理中为我们把相关的事务问题解决了,比如开始事务,遇到异常进行事务回灌等等。在数据库中是通过binlog或者redo log来实现事务提交和回滚的。Spring的事务机制

向我们经常使用的JPA、mybatis,hibernate等数据访问技术都有事务处理机制,他们提供了用来开启事务、提交事务来完成数据操作的相关API,或者在发生错误的时候回滚数据。

当前所有的数据访问技术都能够很友好的和Spring进行集成,Spring统一处理不同数据访问技术的事务处理。在Spring中提供了一个叫做PlatformTransactionManager接口,不同的数据访问技术都会对该接口进行实现,如表所示。

在程序中我们可以使用如下方式来定义事务管理器:

声明式事务

Spring中使用注解@Transactional来声明事务,添加该注解的类和方法,Spring就此处需要事务的支持。这个操作是通过AOP实现的。

特别声明:@Transactional注解来自org.springframework.transaction.annotation包,而不是javax.transaction。

AOP 代理的两种实现

jdk是代理接口,私有方法必然不会存在在接口里,故此也就不会被拦截到;cglib是子类,private的方法照样不会出现在子类里,也不能被拦截。Java 动态代理

步骤:

创建调用处理器,通过实现 InvocationHandler 接口完成;创建动态代理类,通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来完成;使用反射技术获得动态代理类的构造方法,调用处理器接口类型是唯一参数类型;创建动态代理类实例,使用构造方法创建,调用处理器对象作为参数被传入。GCLIB代理

cglib(Code Generation Library)是一个强大的代码生成类库。它能够在运行期扩展Java类与实现Java接口。

cglib封装了asm,可以在运行期动态生成新的class(子类)。cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。原理区别

java动态代理实质是生成了一个实现了代理接口的匿名类,这个过程使用的是反射技术来完成的,在调用具体方法前调用InvokeHandler来处理。

cglib动态代理是对代理对象类的class文件加载进来,修改其字节码生成子类来处理,这个过程是通过asm开源包来实现的,具体大家可以百度,本文不做介绍。

默认情况下,实现了接口的目标对象会采用JDK的动态代理实现AOP也可以强制使用CGLIB实现AOP目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换如果是类内部方法直接不是走代理,这个时候可以通过维护一个自身实例的代理。

Spring 事务的传播属性

Spring事务传播性,就是当多个事务同时存在的时候Spring如何处理这些事务的行为。这些属性都定义在在TransactionDefinition类中,相关常量含义见下表:

数据库隔离级别

脏读:一个事务可以读取到另一个事务未提交的修改后的数据。假设这个事务发生了异常进行了事务回滚,那么这个时候另一个事务就读取到了脏数据。

不可重复读:如果在一个事务中有两次或多次多操作,在这些读取操作之间,另一个事务对数据进行了修改,这时候读取的数据是不一致的。

幻读:一个事务对一定范围的数据进行批量修改,另一个事务在这个范围增加一条数据,此时该事务就会丢失对新增数据的修改。

总结

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

一般数据库默认隔离级别: Read Commited,比如 SqlServer、Oracle

少数数据库默认隔离级别为:Repeatable Read 比如:MySQL InnoDB

Spring中的隔离级别

事务的嵌套

接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制。

假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()

PROPAGATION_REQUIRED(Spring 默认)

如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。

假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW

比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。

那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。

他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。

PROPAGATION_SUPPORTS

假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED

现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

a、捕获异常,执行异常分支逻辑

这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback

另外三种事务传播属性基本用不到,在此不做分析。

总结

对于项目中需要使用到事务的地方,我建议开发者还是使用spring的TransactionCallback接口来实现事务,不要盲目使用spring事务注解,如果一定要使用注解,那么一定要对spring事务的传播机制和隔离级别有个详细的了解,否则很可能发生意想不到的效果。

Spring Boot 对事务的支持

通过org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration类。我们可以看出Spring Boot自动开启了对注解事务的支持 Spring

只读事务(@Transactional(readOnly = true))的一些概念

概念:从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。@Transcational(readOnly=true) 这个注解一般会写在业务类上,或者其方法上,用来对其添加事务控制。当括号中添加readOnly=true, 则会告诉底层数据源,这个是一个只读事务,对于JDBC而言,只读事务会有一定的速度优化。

而这样写的话,事务控制的其他配置则采用默认值,事务的隔离级别(isolation) 为DEFAULT,也就是跟随底层数据源的隔离级别,事务的传播行为(propagation)则是REQUIRED,所以还是会有事务存在,一代在代码中抛出RuntimeException,依然会导致事务回滚。

应用场合:如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。【注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务】

想了解更多精彩内容,快来关注计算机java编程

阅读剩余内容
网友评论
相关内容
延伸阅读
小编推荐

大家都在看

全国甲卷语文作文 参观博物馆英语作文 冬天作文450字 那时花开 作文 关于钓鱼的作文 董存瑞作文 出发作文800字 我有一个想法的作文 打扫房间作文 亲情作文200字 放风筝的作文怎么写 自我介绍日语作文 身边有特点的人作文 成都旅游作文 关爱老人的作文 拔河作文300字 诗与远方作文 变形记作文500字 作文精彩开头和结尾 木头人游戏作文 作文可以写什么题目 我的房子英语作文 考研英语二大作文 关于日出的作文 关于动物园的作文 北京游记作文 成人大专作文 竹子作文300字 再见了老师作文 介绍我的家乡英语作文