Spring事务传播性与隔离级别
事务是逻辑处理原⼦性的保证⼿段,通过使⽤事务控制,可以极⼤的避免出现逻辑处理失败导致的脏数据等问题。
事务最重要的两个特性,是事务的传播级别和数据隔离级别。传播级别定义的是事务的控制范围,事务隔离级别定义的是事务在数据库读写⽅⾯的控制范围。
以下是事务的7种传播级别:
1) PROPAGATION_REQUIRED ,默认的spring事务传播级别,使⽤该级别的特点是,如果上下⽂中已经存在事务,那么就加⼊到事务中执⾏,如果当前上下⽂中不存在事务,则新建事务执⾏。所以这个级别通常能满⾜处理⼤多数的业务场景。
2)PROPAGATION_SUPPORTS ,从字⾯意思就知道,supports,⽀持,该传播级别的特点是,如果上下⽂存在事务,则⽀持事务加⼊事务,如果没有事务,则使⽤⾮事务的⽅式执⾏。所以说,并⾮所有的包在ute中的代码都会有事务⽀持。这个通常是⽤来处理那些并⾮原⼦性的⾮核⼼业务逻辑操作。应⽤场景较少。
3)PROPAGATION_MANDATORY , 该级别的事务要求上下⽂中必须要存在事务,否则就会抛出异常!
配置该⽅式的传播级别是有效的控制上下⽂调⽤代码遗漏添加事务控制的保证⼿段。⽐如⼀段代码不能单独被调⽤执⾏,但是⼀旦被调⽤,就必须有事务包含的情况,就可以使⽤这个传播级别。
4)PROPAGATION_REQUIRES_NEW ,从字⾯即可知道,new,每次都要⼀个新事务,该传播级别的特点是,每次都会新建⼀个事务,并且同时将上下⽂中的事务挂起,执⾏当前新建事务完成以后,上下⽂事务恢复再执⾏。
这是⼀个很有⽤的传播级别,举⼀个应⽤场景:现在有⼀个发送100个红包的操作,在发送之前,要做⼀些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送⽇志,发送⽇志要求100%的准确,如果⽇志不准确,那么整个⽗事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的⼦事务不会直接影响到⽗事务的提交和回滚。
5)PROPAGATION_NOT_SUPPORTED ,这个也可以从字⾯得知,not supported ,不⽀持,当前级别的特点就是上下⽂中存在事务,则挂起事务,执⾏当前逻辑,结束后恢复上下⽂的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩⼩。我们知道⼀个事务越⼤,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩⼩范围。⽐如⼀段代码,是每次逻辑操作都必须
调⽤的,⽐如循环1000次的某个⾮核⼼业务逻辑操作。这样的代码如果包在事务中,势必造成事务太⼤,导致出现⼀些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上⽤场了。⽤当前级别的事务模板抱起来就可以了。
6)PROPAGATION_NEVER ,该事务更严格,上⾯⼀个事务传播级别只是不⽀持⽽已,有事务就挂起,⽽PROPAGATION_NEVER传播级别要求上下⽂中不能存在事务,⼀旦有事务,就抛出runtime异常,强制停⽌执⾏!这个级别上辈⼦跟事务有仇。
7)PROPAGATION_NESTED ,字⾯也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下⽂中存在事务,则嵌套事务执⾏,如果不存在事务,则新建事务。
那么什么是嵌套事务呢?很多⼈都不理解,我看过⼀些博客,都是有些理解偏差。
嵌套是⼦事务套在⽗事务中执⾏,⼦事务是⽗事务的⼀部分,在进⼊⼦事务之前,⽗事务建⽴⼀个回滚点,叫save point,然后执⾏⼦事务,这个⼦事务的执⾏也算是⽗事务的⼀部分,然后⼦事务执⾏结束,⽗事务继续执⾏。重点就在于那个save point。看⼏个问题就明了
了:
如果⼦事务回滚,会发⽣什么?
⽗事务会回滚到进⼊⼦事务前建⽴的save point,然后尝试其他的事务或者其他的业务逻辑,⽗事务之前的操作不会受到影响,更不会⾃动回滚。
如果⽗事务回滚,会发⽣什么?
⽗事务回滚,⼦事务也会跟着回滚!为什么呢,因为⽗事务结束之前,⼦事务是不会提交的,我们说⼦事务是⽗事务的⼀部分,正是这个道理。那么:
事务的提交,是什么情况?
是⽗事务先提交,然后⼦事务提交,还是⼦事务先提交,⽗事务再提交?答案是第⼆种情况,还是那句话,⼦事务是⽗事务的⼀部分,由⽗事务统⼀提交。
现在你再体会⼀下这个”嵌套“,是不是有那么点意思?
以上是事务的7个传播级别,在⽇常应⽤中,通常可以满⾜各种业务需求,但是除了传播级别,在读取数据库的过程中,如果两个事务并发执⾏,那么彼此之间的数据是如何影响的呢?
这就需要了解⼀下事务的另⼀个特性:数据隔离级别
数据隔离级别分为不同的四种:
1、Serializable :最严格的级别,事务串⾏执⾏,资源消耗最⼤;
2、REPEATABLE READ :保证了⼀个事务不会修改已经由另⼀个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
3、READ COMMITTED :⼤多数主流数据库的默认事务等级,保证了⼀个事务不会读到另⼀个并⾏事务已修改但未提交的数据,避免
了“脏读取”。该级别适⽤于⼤多数系统。
4、Read Uncommitted :保证了读取过程中不会读取到⾮法数据。
上⾯的解释其实每个定义都有⼀些拗⼝,其中涉及到⼏个术语:脏读、不可重复读、幻读。
这⾥解释⼀下:
脏读 :所谓的脏读,其实就是读到了别的事务回滚前的脏数据。⽐如事务B执⾏过程中修改了数据X,在未提交前,事务A读取了X,⽽事务B 却回滚了,这样事务A就形成了脏读。
不可重复读 :不可重复读字⾯含义已经很明了了,⽐如事务A⾸先读取了⼀条数据,然后执⾏逻辑的
时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。
幻读 :⼩的时候数⼿指,第⼀次数⼗10个,第⼆次数是11个,怎么回事?产⽣幻觉了?
幻读也是这样⼦,事务A⾸先根据条件索引得到10条数据,然后事务B改变了数据库⼀条数据,导致也符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产⽣了幻读。
⼀个对照关系表:
Dirty reads          non-repeatable reads            phantom reads
Serializable                          不会                        不会                                          不会
REPEATABLE READ            不会                        不会                                            会
READ COMMITTED            不会                        会                                                会
Read Uncommitted            会                          会                                                会
所以最安全的,是Serializable,但是伴随⽽来也是⾼昂的性能开销。
另外,事务常⽤的两个属性:readonly和timeout
⼀个是设置事务为只读以提升性能。
另⼀个是设置事务的超时时间,⼀般⽤于防⽌⼤事务的发⽣。还是那句话,事务要尽可能的⼩!
session如何设置和读取最后引⼊⼀个问题:
⼀个逻辑操作需要检查的条件有20条,能否为了减⼩事务⽽将检查性的内容放到事务之外呢?
很多系统都是在DAO的内部开始启动事务,然后进⾏操作,最后提交或者回滚。这其中涉及到代码设计的问题。⼩⼀些的系统可以采⽤这种⽅式来做,但是在⼀些⽐较⼤的系统,
逻辑较为复杂的系统中,势必会将过多的业务逻辑嵌⼊到DAO中,导致DAO的复⽤性下降。所以这不是⼀个好的实践。
来回答这个问题:能否为了缩⼩事务,⽽将⼀些业务逻辑检查放到事务外⾯?答案是:对于核⼼的业务检查逻辑,不能放到事务之外,⽽且必须要作为分布式下的并发控制!
⼀旦在事务之外做检查,那么势必会造成事务A已经检查过的数据被事务B所修改,导致事务A徒劳⽆功⽽且出现并发问题,直接导致业务控制失败。
所以,在分布式的⾼并发环境下,对于核⼼业务逻辑的检查,要采⽤加锁机制。
⽐如事务开启需要读取⼀条数据进⾏验证,然后逻辑操作中需要对这条数据进⾏修改,最后提交。
这样的⼀个过程,如果读取并验证的代码放到事务之外,那么读取的数据极有可能已经被其他的事务修改,当前事务⼀旦提交,⼜会重新覆盖掉其他事务的数据,导致数据异常。
所以在进⼊当前事务的时候,必须要将这条数据锁住,使⽤for update就是⼀个很好的在分布式环境下的控制⼿段。
⼀种好的实践⽅式是使⽤编程式事务⽽⾮⽣命式,尤其是在较为规模的项⽬中。对于事务的配置,在代码量⾮常⼤的情况下,将是⼀种折磨,⽽且⼈⾁的⽅式,绝对不能避免这种问题。
将DAO保持针对⼀张表的最基本操作,然后业务逻辑的处理放⼊manager和service中进⾏,同时使⽤编程式事务更精确的控制事务范围。特别注意的,对于事务内部⼀些可能抛出异常的情况,捕获要谨慎,不能随便的catch Exception 导致事务的异常被吃掉⽽不能正常回滚。
Spring配置声明式事务:
* 配置SessionFactory
* 配置事务管理器
* 事务的传播特性
* 那些类那些⽅法使⽤事务
编写业务逻辑⽅法
* 继承HibernateDaoSupport类,使⽤HibernateTemplate来持久化,HibernateTemplate是
Hibernate Session的轻量级封装
* 默认情况下运⾏期异常才会回滚(包括继承了RuntimeException⼦类),普通异常是不会滚的
* 编写业务逻辑⽅法时,最好将异常⼀直向上抛出,在表⽰层(struts)处理
* 关于事务边界的设置,通常设置到业务层,不要添加到Dao上
<!-- 配置SessionFactory -->
<bean id="sessionFactory" class="hibernate3.LocalSessionFactoryBean">
<property name="configLocation">
<value>classpath:l</value>
</property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="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 expression="execution(* com.service.*.*(..))" id="transactionPC"/>
<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>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。