理解 spring 事务传播行为与数据隔离级别
事务,是为了保障逻辑处理的原子性、一致性、隔离性、永久性。
通过事务控制,可以避免因为逻辑处理失败而导致产生脏数据等等一系列的问题。
事务有两个重要特性:
- 事务的传播行为
- 数据隔离级别
1、事务传播行为(Transaction Behavior)
传播行为级别,定义的是事务的控制范围。通俗点说,执行到某段代码时,对已存在事务的不同处理方式。
Spring 对 JDBC 的事务隔离级别进行了补充和扩展,并提出了 7 种事务传播行为。
1)Spring 中提供的 7 种传播行为
PROPAGATION_REQUIRED,需要事务处理。有则使用,无则新建。这是 Spring 默认的事务传播行为。该级别的特性是,如果 Context 中已经存在事务,那么就将当前需要使用事务的代码加入到 Context 的事务中执行,如果当前 Context 中不存在事务,则新建一个事务执行代码。这个级别通常能满足大多数的业务场景。
PROPAGATION_SUPPORTS,支持事务处理。该级别的特性是,如果 Context 存在事务,则将代码加入到 Context 的事务中执行,如果 Context 中没有事务,则使用 非事务 的方式执行。
PROPAGATION_MANDATORY,强制性要求事务。该级别的特性是,当要以事务的方式执行代码时,要求 Context 中必须已经存在事务,否则就会抛出异常!使用 MANDATORY 强制事务,可以有效地控制 “必须以事务执行的代码,却忘记给它加上事务控制” 这种情况的发生。举个简单的例子:有一个方法,对这个方法的要求是一旦被调用,该方法就必须包含在事务中才能正常执行,那么这个方法就适合设置为 PROPAGATION_MANDATORY 强制事务传播行为,从而在代码层面加以控制。
PROPAGATION_REQUIRES_NEW,每次都新建一个事务。该级别的特点是,当执行到一段需要事务的代码时,先判断 Context 中是否已经有事务存在,如果不存在,就新建一个事务;如果已经存在,就 suspend 挂起当前事务,然后创建一个新事务去执行,直到新事务执行完毕,才会恢复先前挂起的 Context 事务。
PROPAGATION_NOT_SUPPORTED,不支持事务。该级别的特点是,如果发现当前 Context 中有事务存在,则挂起该事务,然后执行逻辑代码,执行完毕后,恢复先前挂起的 Context 事务。这个传播行为的事务,可以缩小事务处理过程的范围。举个简单例子,在一个事务中,需要调用一段非核心业务的逻辑操作 1000 次,如果将这段逻辑放在事务中,会导致该事务的范围变大、生命周期变长,为了避免因事务范围扩大、周期变长而引发一些的事先没有考虑到的异常情况发生,可以将这段逻辑设置为 NOT_SUPPORTED 不支持事务传播行为。
PROPAGATION_NEVER,对事务要求更严格,不能出现事务!该级别的特点是,设置了该级别的代码,在执行前一旦发现 Context 中有事务存在,就会抛出 Runtime 异常,强制停止执行,有我无他!
PROPAGATION_NESTED,嵌套事务。该级别的特点是,如果 Context 中存在事务 A,就将当前代码对应的事务 B 加入到 事务 A 内部,嵌套执行;如果 Context 中不存在事务,则新建事务执行代码。换句话说,事务 A 与事务 B 之间是父子关系,A 是父,B 是子。理解嵌套事务的关键点是:save point。
父、子事务嵌套、save point 的说明:
- 父事务会在子事务进入之前创建一个 save point;
- 子事务 rollback ,父事务只会回滚到 save point,而不会回滚整个父事务;
- 父事务 commit 之前,必须先 commit 子事务。
2)代码举例说明
我在网上看到有一篇文章,采用代码的方式来解释事务传播行为级别,代码方式很清晰,一看就明白了。
首先准备如下两个 Service:
class ServiceA {
void methodA() {
ServiceB.methodB();
}
}
class ServiceB {
void methodB() {
}
}
- 若
ServiceB.methodB()的传播行为定义为PROPAGATION_REQUIRED, 那么在执行ServiceA.methodA()的时候,若ServiceA.methodA()已经开启了事务,这时调用ServiceB.methodB(),ServiceB.methodB()将会运行在ServiceA.methodA()的事务内部,而不再开启新的事务。而假如ServiceA.methodA()运行的时候发现自己没有在事务中,就会为它分配一个新事务。这样,在ServiceA.methodA()或者在ServiceB.methodB()内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB()的事务已经被
提交,但是ServiceA.methodA()在接下来的过程中 fail 要回滚,ServiceB.methodB()也会跟着一起回滚。 - 假如
ServiceA.methodA()的传播行为设置为PROPAGATION_REQUIRED,ServiceB.methodB()的传播行为为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB()的时候,ServiceA.methodA()所在的事务就会挂起,而ServiceB.methodB()会起一个新的事务,等待ServiceB.methodB()的事务完成以后,A的事务才会继续执行。PROPAGATION_REQUIRED与PROPAGATION_REQUIRES_NEW的事务区别在于事务的回滚程度。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,如果它抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能会提交。 - 假如
ServiceA.methodA的事务传播行为是PROPAGATION_REQUIRED,而ServiceB.methodB的事务传播行为是PROPAGATION_NOT_SUPPORTED,那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而ServiceB.methodB以非事务的状态运行完之后,再继续ServiceA.methodA的事务。 - 假如
ServiceA.methodA的事务传播行为是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER,那么ServiceB.methodB执行时就会抛出异常。
2、数据隔离级别(Isolation Level)
在读取数据库的过程中,如果两个事务并发执行,那么多个事务彼此之间,会对数据产生什么样的影响呢?
这里就引出了事务的第二个特性:数据隔离级别。
数据隔离级别,定义的是事务在数据库端读写方面的控制范围。
数据隔离级别分为 4 种:
- Serializable:串行化。这是最严格的隔离级别,多个事务之间串行执行,资源消耗极大。
- Repeatable Read:可重复读。该级别可以确保一个已经被事务读取的数据,另一个事务不能修改这个数据,从而避免了 “脏读” 和 “不可重复读”。仍然有较大的性能损耗。
- Read Commited:这是大部分主流数据库默认的数据隔离级别。该级别下,只允许读已经提交的数据。例如:当一个事务修改了数据但未提交时,另一个并行事务只会读到该数据修改之前的内容,从而避免了 “脏读”。
- Read Uncommited:一个事务可以读取另一个并行事务已修改但还未提交的数据。会产生 “脏读”。
第 1 种数据准确性最高,但相应地性能最差。第 4 种性能高,但是相应地读取数据的准确性低。
3、脏读、幻读、不可重复读
脏读、幻读、不可重复读都是并发事务的情况下,因为不同的数据隔离级别而读取到不同的内容。
脏读(Dirty Reads)
脏读,即一个事务读到了另一个事务还未提交的数据。如果脏读读取到的数据最终还是提交了倒还好,但如果这条数据最终回滚了,那么这条数据对于刚刚读取到它的事务而言,就是一条脏数据。
不可重复读(Non-repeatable Reads)
不可重复读,不同的事务读取同一条数据,读取到的内容是不同的。也就是说,对某一条数据而言,不同的事务以同样的重复操作读取,却产生了不同的结果。
幻读(Phantom Reads)
幻读,一个事务按照某种查询条件,第一次读取的数据量和第二次读取的数据量不一样,就像幻觉一样,明明刚才查的是 N 条数据,再查一次就变成了 M 条(M <> N)。
4、如何缩小事务?
假设一个逻辑操作需要检查的条件有 20 个,能否为了减小事务而将检查性的内容放到事务之外呢?
很多系统都是在 DAO 的内部开始启动事务,然后进行操作,最后提交或者回滚。这其中涉及到代码设计的问题。
小一些的系统可以采用这种方式来做,但是在一些比较大的系统,逻辑较为复杂的系统中,势必会将过多的业务逻辑嵌入到 DAO 中,导致 DAO 的复用性下降。所以这不是一个好的实践。
来回答这个问题,能否为了缩小事务,而将一些业务逻辑检查放到事务外面?
答案是:对于核心的业务检查逻辑,不能放到事务之外,而且必须要作为分布式下的并发控制!一旦在事务之外做检查,那么势必会造成事务A已经检查过的数据被事务B所修改,导致事务A徒劳无功而且出现并发问题,直接导致业务控制失败。
所以,在分布式的高并发环境下,对于核心业务逻辑的检查,要采用加锁机制。
比如事务开启需要读取一条数据进行验证,然后逻辑操作中需要对这条数据进行修改,最后提交。
这样的一个过程,如果读取并验证的代码放到事务之外,那么读取的数据极有可能已经被其他的事务修改,当前事务一旦提交,又会重新覆盖掉其他事务的数据,导致数据异常。
所以在进入当前事务的时候,必须要将这条数据锁住,例如使用 Oracle 的 for update 就是一个在分布式环境下很有效的控制手段。
另一种好的实践方式是使用编程式事务而非声明式事务,尤其是在较大规模的项目中。对于大量事务的声明配置,在代码量非常大的情况下,将是一种折磨。
将 DAO 保持针对一张表的最基本操作,然后业务逻辑的处理放入 manager 或 service 中进行,同时使用编程式事务可以更精确地控制事务范围。
特别注意的,对于事务内部一些可能抛出异常的情况,捕获异常时要谨慎,不能随便的 catch Exception,不然会导致事务的异常被吃掉而不能正常回滚。
5、spring 配置声明式事务
Spring配置声明式事务:
- 配置SessionFactory
- 配置事务管理器
- 事务的传播特性
- 声明哪些类,哪些方法需要使用事务
编写业务逻辑方法:
- 默认情况下运行期异常才会回滚(包括继承了RuntimeException子类),普通异常是不会回滚的
- 编写业务逻辑方法时,最好将异常一直向上抛出,在表示层(view)处理
- 关于事务边界的设置,通常设置到业务层,不要添加到 Dao 上。
(1)使用 xml 配置方式:
<!-- 配置SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
</bean>
<!-- 配置 Hibernate 事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 定义通知:定义事务的传播行为 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 声明哪些类哪些方法需要使用事务 -->
<aop:config>
<aop:pointcut id="transactionPC" expression="execution(* com.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPC"/>
</aop:config>
<!-- 普通 IOC 注入 -->
<bean id="userManager" class="com.service.UserManagerImpl">
<property name="logManager" ref="logManager"/>
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="logManager" class="com.service.LogManagerImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
关于 spring 配置中 read-only 的说明:
read-only 配置为 true,会告诉 spring 对应的事务应该被最优化为只读事务。
这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用 Object/Relational 映射工具(如:Hibernate 或 TopLink)时避免 dirty checking(试图“刷新”)。
(2)使用 JPA 注解方式
@Service
public class UserServiceImpl implements IUserService {
@Resource
IUserDAO userDAO;
//启动 REQUIRED 默认事务传播行为的方法
@Transactional
public void funNone() throws Exception {
save(new UserEntity("aaa"));
}
//启动 REQUIRED 默认事务传播行为的方法
@Transactional(propagation = Propagation.REQUIRED)
public void funRequire() throws Exception {
save(new UserEntity("bbb"));
}
//启动 Nested 嵌套事务的方法
@Transactional(propagation = Propagation.NESTED)
public void funNest() throws Exception {
save(new UserEntity("ccc"));
}
//REQUIRES_NEW 事务的方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void funRequireNew() throws Exception {
save(new UserEntity("ddd"));
}
}
参考文章:
(Spring事务传播性与隔离级别)[http://blog.csdn.net/edward0830ly/article/details/7569954]
http://blog.sina.com.cn/s/blog_4b5bc0110100z7jr.html
理解 spring 事务传播行为与数据隔离级别的更多相关文章
- Spring事务传播机制和数据库隔离级别
Spring事务传播机制和数据库隔离级别 转载 2010年06月26日 10:52:00 标签: spring / 数据库 / exception / token / transactions / s ...
- 深入理解 Spring 事务原理
本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一.事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供 ...
- 深入理解 Spring 事务原理【转】
本文转自码农网 – 吴极心原创 连接地址:http://www.codeceo.com/article/spring-transactions.html 一.事务的基本原理 Spring事务的本质其 ...
- 理解 Spring 事务原理
转载:https://www.jianshu.com/p/4312162b1458 一.事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事 ...
- 深入理解Spring事务的那点事
Spring事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: 获 ...
- 数据库事务的四大特性以及事务的隔离级别-与-Spring事务传播机制&隔离级别
数据库事务的四大特性以及事务的隔离级别 本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别. 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ ...
- Spring事务传播机制与隔离级别(转)
Spring事务传播机制与隔离级别 博客分类: Spring 转自:http://blog.csdn.net/edward0830ly/article/details/7569954 (写的不错) ...
- 什么是事务?事务特性?事务隔离级别?spring事务传播特性?
一.事务的概述 什么是事务? 在数据库中,所谓事务是指一组逻辑操作单元即一组sql语句,当这个单元中的一部分操作失败,整个事务回滚,只有全部正确才完成提交.判断事务是否配置成功的关键点在于出现异常时事 ...
- 事务、事务特性、事务隔离级别、spring事务传播特性
事务.事务特性.事务隔离级别.spring事务传播特性 1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功, ...
随机推荐
- [RN] Android 设备adb连接后unauthorized解决方法
Android 设备adb连接后unauthorized解决方法 安卓设备usb或者adbwireless连接后输入adb device后都是未授权状态 相信很多同学都会遇到这种情况,除了一直重复开关 ...
- 以V8中js源码为例了解GitHub查看代码功能
GitHub作为开源仓库,许多开源项目仓库这里,当然不乏十分优秀的,比如Node.V8,我一直比较好奇js源码,像java的话,因为环境是JDK,我们结合IDE很容易就能跳转到其源码内部去查看实现,但 ...
- 关于“100g文件全是数组,取最大的100个数”解决方法汇总
原题如下: 有一个100G大小的文件里存的全是数字,并且每个数字见用逗号隔开.现在在这一大堆数字中找出100个最大的数出来. 我认为,首先要摸清考官的意图.是想问你os方面的知识,还是算法,或者数据结 ...
- 《Linux就该这么学》培训笔记_ch17_使用iSCSI服务部署网络存储
<Linux就该这么学>培训笔记_ch17_使用iSCSI服务部署网络存储 文章最后会post上书本的笔记照片. 文章主要内容: iSCSI技术介绍 创建RAID磁盘阵列 配置iSCSI服 ...
- hive 批量添加,删除分区
一.批量添加分区: use bigdata; alter table siebel_member add if not exists partition(dt='20180401') locati ...
- DIY Images
正如你想到的,我们当然也想自己做一个属于自己的特别的图案吧. 其实很简单 25个中每一个led灯都是可以单独控制的,每一个灯都设10个级别,如果设置在0,则不发光,如果设置为9,则是最亮,1~8,则是 ...
- 微信公众号 --- 获取access_token
获取access_token 在左侧菜单栏中也可以找到 可以一步步的进行设置 , 身份验证的时候要 注意:密码是你创建微信公众号的密码 往一步步的执行就可以了 接下来就是获取ip 白名单,进行设置 ...
- something want to write
1.时间戳不能相信是因为机器时间有误差.相当于机器不断电的跑着时钟. 2.写log的时候记得log别人的ip,不然没办法很好的debug.
- 模拟 + 暴搜 --- Help Me with the Game
Help Me with the Game Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 3175 Accepted: ...
- FastReport 程序员手册
一.使用TfrxReport 组件工作1.加载并存储报表默认情况下,报表窗体同项目窗体构存储在同一个DFM文件中.多数情况下,无须再操作,因而你就不必采用特殊方法加载报表.如果你决定在文件中存储报表窗 ...