九亭做网站友情链接你会回来感谢我
文章目录
- 概述
- 1. 什么是spring的事务
- 2. 两种使用事务的方式
- 2.1 编程式事务管理
- 2.2 声明式事务管理
- 3. 事务的原理
- 4. Spring 事务的传播属性
- 4.1 什么是默认事务传播类型
- 4.2 什么是事务挂起
- 4.3 PROPAGATION_NESTED详解
- 4.4 声明式事务的陷阱
- 5. 数据库隔离级别与Spring中的隔离级别
- 5.1 数据库隔离级别
- 5.2 Spring中的隔离级别
- 5.3 注意事项
- 参考
相关文章
spring 的事务(transaction) 一 基础概念介绍
spring 的事务(transaction) 二 陷阱
spring 的事务(transaction) 三 try catch对事务的影响
spring 的事务(transaction) 四 嵌套事务PROPAGATION_NESTED
概述
spring事务的原理是什么?
- 首先mysql这样的数据库本身是支持事务的,有不同的事务隔离级别,事务分为手动开启事务和自动开启事务,参见 【mysql】MYSQL事务的开启与提交命令答疑,通过底层的支持,可以实现多条sql 原子化,要么都执行,要么都不执行
- spring事务采用注解生成代理对象,把默认的自动开启事务变为手动开启,这样 多条sql语句都执行完后,才会提交事务
1. 什么是spring的事务
Spring事务 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:
事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。
1. 获取连接 Connection con = DriverManager.getConnection()2. 开启事务con.setAutoCommit(true/false);3. 执行CRUD4. 提交事务/回滚事务 con.commit() / con.rollback();5. 关闭连接 conn.close();
使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。
那么Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了。下面简单地介绍下,注解方式为例子:
配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
spring事务的实现可以当做是springAOP和IOC的结合使用。设计原理就是使用AOP实现了声明式事务处理的interceptor,封装了对spring的处理过程。
真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
2. 两种使用事务的方式
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是声明式的,
@Transactional注解就是声明式的。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
2.1 编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
2.2 声明式事务管理
声明式事务也分为多种具体的种类,比如xml配置、注解元数据驱动(@Transactional注解)
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
3. 事务的原理
spring事务的实现可以当做是springAOP和IOC的结合使用。设计原理就是使用AOP实现了声明式事务处理的interceptor,封装了对spring的处理过程。
声明式事务实现首先需要配置aop环境,生成代理对象TransactionProxy和TrasactionInterceptor。封装不同的事务处理,整合具体实现到AOP和IOC。提供了即开即用的事务服务功能。在事务处理过程中,TransactionInfo和TransactionStatus是事务处理信息和状态的数据存储的对象。后面的事务处理都需要根据状态来进行的,当做参数传入。具体的事务处理是交给TransactionManager管理的,他提供了通用的事务处理模板,如doCommit等。具体的操作由具体的事务处理器来实现。
在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。
当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。
4. Spring 事务的传播属性
事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
这些属性在TransactionDefinition中定义,常用的事务传播机制如下:
-
PROPAGATION_REQUIRED
Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行 -
PROPAGATION_REQUES_NEW
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可 -
PROPAGATION_SUPPORT
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务 -
PROPAGATION_NOT_SUPPORT
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码 -
PROPAGATION_NEVER
该传播机制不支持外层事务,即如果外层有事务就抛出异常 -
PROPAGATION_MANDATORY
与NEVER相反,如果外层没有事务,则抛出异常 -
PROPAGATION_NESTED
嵌套事务呈现父子事务概念,二者之间是有关联的,核心思想就是子事务不会独立提交,而是取决于父事务,当父事务提交,那么子事务才会随之提交;如果父事务回滚,那么子事务也回滚。但是子事务又有自己的特性,那 就是可以独立进行回滚,不会引发父事务整体的回滚(当然需要try catch子事务,避免异常传递至父层事务,如果没有,则也会引发腐父事务整体回滚)。这个特性比较有意思,虽然不能独立提交,但是可以独立回滚,因此,如果存在ABC 三个子事务,那么每个子事务都可以独立回滚,子事务类似一个游戏中的保存点,假设某个时间点,创建了一个保存点A,角色有10发子弹,主线继续发生时,对应执行某个子事务内的逻辑,如果游戏角色打了4发子弹,剩余6发子弹时挂了,点击返回上一个保存点,可以重新玩一次,此时该角色又是10发子弹,对应的就是子事务发生异常,子事务回滚到事务执行之前的那个点,放佛从来没有执行过该子事务一样,数据库的数据也不会发生变更。
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。
常用的:
PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEWPROPAGATION_NESTED
4.1 什么是默认事务传播类型
要注意默认事务传播类型和未设置事务的区别。
默认事务传播的前提是指通过配置或编码,引入事务,如果没有指定事务的传播类型,此时默认的事务传播类型为PROPAGATION_REQUIRED。
而未设置事务,就是没有事务。
4.2 什么是事务挂起
为了方便理解,我们把独立的2个事务,称之为外层事务和里面事务,如果2个事务相关,比如下文的PROPAGATION_NESTED ,此时可以称之为父子事务,表示关系更密切。
事务挂起一般是在出现独立事务的场景,即类似下面,外层和里层都有事务定义:
ServiceA { /** * 事务属性配置为 PROPAGATION_REQUIRED */ void methodA() { dosomethingBefore();ServiceB.methodB(); dosomethingAfter();} } ServiceB { /** * 事务属性配置为 PROPAGATION_REQUES_NEW*/ void methodB() { } }
注意我们上面代码中的是 2个不同的Service,而不是同一个service里面的2个不同方法,这涉及到陷阱问题,下文我们会讲。
ServiceA 的methodA()是外层事务,而ServiceB 的methodB()内会开启一个内层事务,此时这个内层事务是独立的,你可以把他理解成是个私密的空间,和外层互不干扰。可以理解成外层事务看不见内层事务,对于外层事务来说,甚至压根就不知道该内层事务的存在,当代码执行到内层事务时,外层事务就睡觉了(这就是挂起),当内层事务执行完,外层事务醒来,接着执行其余逻辑,好像对外层事务来说,什么多余的事情也没发生。
4.3 PROPAGATION_NESTED详解
PROPAGATION_NESTED 和PROPAGATION_REQUIRES_NEW的区别:
从上面可以看出,PROPAGATION_REQUIRES_NEW是平级关系,兄弟关系,二者之间没有关联;而PROPAGATION_NESTED 是类似父子关系。
有关更详细的信息,参见 spring 的事务(transaction) 四 嵌套事务PROPAGATION_NESTED
4.4 声明式事务的陷阱
5. 数据库隔离级别与Spring中的隔离级别
5.1 数据库隔离级别
隔离级别 隔离级别的值 导致的问题
Read-Uncommitted 0 导致脏读
Read-Committed 1 避免脏读,允许不可重复读和幻读
Repeatable-Read 2 避免脏读,不可重复读,允许幻读
Serializable 3 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重
- 脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。 - 幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
总结:
- 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
- 大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle
- 少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB
5.2 Spring中的隔离级别
常量 解释
ISOLATION_DEFAULT 这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
5.3 注意事项
数据库的配置隔离级别是Read Commited,而Spring配置的隔离级别是Repeatable Read,请问这时隔离级别是以哪一个为准?
以Spring配置的为准,如果spring设置的隔离级别数据库不支持,效果取决于数据库。
参考
【Spring 核心】(4):Spring事务 的实现
有关Spring事务,看这一篇就足够了
事务之六:spring 嵌套事务 列出多个样例
嵌套事务总结 更多的例子