概念
- 为了保证对于数据库的每一步操作都是可靠的,即使出现了异常情况,也不至于破坏数据的完整性。
四大特征,或者说保证
- 原子性
(Atomicity)
整个事务的所有操作,要么全部失败,要么全部成功,不可能停留在某个环节(受限于事务的超时时间),发生错误时会回滚到一切操作执行的状态 - 一致性
(Correspondence)
在事务开始前与结束后,数据库的完整性约束不被破坏 - 隔离性
(Isolation)
隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。 - Durability
(Durability)
在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
五大隔离级别
-
TransactionDefinition.ISOLATION_DEFAULT:
默认值,表示使用底层数据库的隔离级别。对于大多数数据库而言,通常此值就是TransactionDefinition.ISOLATION_READ_COMMITTED。 -
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:
一个事务可以读取另一个事务已经修改但还未提交的数据,不能防止脏读与不可重复读,因此极少使用 -
TransactionDefinition.ISOLATION_READ_COMMITTED:
一个事务只可以读取另一个事务已经提交的数据,可以防止脏读,为大多数情况下的推荐值。 -
TransactionDefinition.ISOLATION_REPEATABLE_READ:
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。 -
TransactionDefinition.ISOLATION_SERIALIZABLE:
所有事务串行,可以防止脏读、不可重复读以及幻读,但会严重影响效率
七种传播行为
传播行为是指:如果在开始一个事务之前,已经存在一个事务上下文,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:(下面以小红和她男朋友去上班的故事简述)
参数 | 说明 |
---|---|
PROPAGATION_REQUIRED |
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务,这是spring默认的传播行为 |
PROPAGATION_REQUIRES_NEW |
创建一个新的事务,如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_SUPPORTS |
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
PROPAGATION_NOT_SUPPORTED |
以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER |
以非事务方式运行,如果当前存在事务,则抛出异常 |
PROPAGATION_MANDATORY |
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
PROPAGATION_NESTED |
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED |
-
TransactionDefinition.PROPAGATION_REQUIRED:
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务,这是spring默认的传播行为。
小红和她男朋友关系很好,每天上班男朋友都会开车来接她,但也会有例外情况,所以小红一般会乘坐男朋友车,如果男朋友没能来就把自己的车开出来走 -
TransactionDefinition.PROPAGATION_REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则把当前事务挂起。
小红与男朋友闹矛盾了,她决定接下来几天都自己开车上班,即便男朋友来接她,也要开自己车走,哼╭(╯^╰)╮。 -
TransactionDefinition.PROPAGATION_SUPPORTS:
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
小红和男朋友很快和好如初了,于是男朋友来接自己的时候,就会坐男朋友车一起走,可是有天小红自己开车时车子轮胎坏掉了=-=,只能送到4S店维修了=-=所以男朋友没能来接自己的时候就只能步行去公司了=-=没办法,谁让自己住的地方偏僻连公交都没有呢=-= -
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
小红最近伙食有点好,着实有些发福了,看着日益丰满的小肚子小红咬咬牙决定减肥:每天餐饮减半、步行上班,男朋友很是心疼,每天上班时都会来她家接她,不忍心她步行上班,可是她低估了一个姑娘的决心!哼,小红是绝对不会像资本主义低头的,步行上班,节能环保! -
TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。
小红的依然每天来接小红,小红害怕自己坚定的意志会被动摇,毕竟暖暖的车厢确实比走路上班舒胡多了鸭,于是小红告诉男票:再来动摇自己的决心,小心我和你生气!!(抛出异常) -
TransactionDefinition.PROPAGATION_MANDATORY:
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
小红最近来了例假,却是不能再减肥了,毕竟伤身体的事情还是要杜绝的,可车子还在4S店没有修好,小红就给男朋友下了硬性指标:必须来接我上班,否则我就向未来婆婆大人告状!╭(╯^╰)╮(抛出异常) -
TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
小红最近在网上买了一辆炒鸡可耐的自行车,节能环保绿色出行的好伙伴呐,小红爱不释手于是每天上班都骑车,即便男朋友来接自己也要把自行车放到男票后备箱,待下班后回家路上骑(至于家庭轿车后备箱为什么这么大。。别问我我也不知道~),男票不来就更好咯,自己骑车上班=-=嘟嘟嘟
这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
事务的只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
事务的回滚规则
通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务。
编程式事务管理
首先配置PlatformTransactionManager的beans
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
其次在要使用事务的beans中注入PlatformTransactionManager
@Autowired
private PlatformTransactionManager transactionManager;
方法中使用
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// ... 执行业务
// 执行完后提交事务
transactionManager.commit(status);
} catch(Exception e) {
transactionManager.rollback(status);
}
很少使用了,这里就不在介绍了
声明式事务管理
- 基于 <tx> 命名空间的声明式事务管理
Spring 2.x 引入了 <tx> 命名空间,结合使用 <aop> 命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于 <aop> 命名空间的切点表达式支持,声明式事务也变得更加强大。
<bean id="bankService"
class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<tx:advice id="bankAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
</aop:config>
如果默认的事务属性可以满足要求,可以使用简化版:
<bean id="bankService"
class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<tx:advice id="bankAdvice" transaction-manager="transactionManager">
<aop:config>
<aop:pointcut id="bankPointcut" expression="execution(**.transfer(..))"/>
<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
</aop:config>
由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。另外,如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。
- 基于 @Transactional 的声明式事务管理
除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
return bankDao.transfer(fromId, toId, amount);
}
Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean:
<tx:annotation-driven transaction-manager="transactionManager"/>
与前面相似,transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
基于 <tx> 命名空间和基于 @Transactional 的事务声明方式各有优缺点。基于 <tx> 的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于 @Transactional 的方式必须在每一个需要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多数事务的规则是一致的,但是对 @Transactional 而言,也无法重用,必须逐个指定。另一方面,基于 @Transactional 的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。
- 声明式事务的问题
- 同一个类的方法相互调用,会使用同一个事务,事务的传播行为在同类方法间的调用失效
- 同一个类的方法相互调用时,假设A调用B,若A没有事务,则B不可能拥有事务
后记
PROPAGATION_NESTED
嵌套事务与PROPAGATION_REQUIRES_NEW
事务区别
- 假设有方法A与方法B, A存在外部事务,B存在内部事务,A方法的事务包含了调用B方法
public void A() {
// 外部事务
B();
}
public void B() {
// 内部事务
}
- B方法的内部事务分别使用
PROPAGATION_REQUIRES_NEW
与PROPAGATION_NESTED
- 这里根据两个事务的不同行为进行比较
-
new_result_B
表示使用B方法使用PROPAGATION_REQUIRES_NEW
时,B的结果 -
new_result_A
表示使用B方法使用PROPAGATION_REQUIRES_NEW
时,A的结果 -
nested_result_B
表示使用B方法使用PROPAGATION_NESTED
时,B的结果 -
nested_result_A
表示使用B方法使用PROPAGATION_NESTED
时,A的结果
B行为 | A行为 | new_result_B | new_result_A | nested_result_B | nested_result_A |
---|---|---|---|---|---|
提交 | 提交 | 成功 | 成功 | 成功 | 成功 |
提交 | 回滚 | 成功 | 失败 | 失败 | 失败 |
回滚 | 提交 | 失败 | 成功 | 失败 | 成功 |
回滚 | 回滚 | 失败 | 失败 | 失败 | 失败 |
- 总结:内部嵌套事务,拥有自己的回滚权,但没有提交权。
但new事务,既有自己的回滚权,同时具有提交权。
但相同的是,他们都不会对外部事务造成影响
网友评论