Spring事务管理简介
- Spring为多种不同类型的事务管理机制提供统一编程模型,这些事务管理模型包括JTA、JDBC、Hibernate、JPA和JDO.
- Spring支持声明式事务管理(使用XML文档配置(或者Annotation)结合AOP实现的事务管理).
- 为代码嵌入式(programmatic)的事务管理提供API接口,与复杂的JTA接口相比要简单的多.
- 能够与Spring的数据抽象访问完美结合.
Spring 事务管理优点
JavaEE中有两种类型的事务管理方式,分别是全局(global)和局部(local)事务, 它们各自有优缺点.
全局事务
全局事务主要是指通过JTA管理多个数据库或者消息队列的事务处理。JTA接口使用复杂,并且必须结合JNDI才能使用,因此限制了应用代码潜在的重用性.
EJB的CMT(Container Manager Transaction),使用声明式事务管理,无需使用JNDI的支持,但是需要引入EJB相应的服务.
局部事务
局部事务是资源(数据库)相关的,例如JDBC的connection。尽管局部事务使用简单,但是其缺点也是很明显的:局部事务不能在多个源(数据库)间统一管理事务,此外事务管理使用嵌入式代码,不利于代码逻辑的清晰展现.
Spring 事务
Spring解决了全局和局部事务的缺陷,使用一致性编程模型,一次编码,可以在不同的事务策略和运行环境中迁移。此外,Spring提供两种类型的事务管理方式,分别是代码嵌入式和声明式,更多的人喜欢使用声明式.
Spring的事务管理依赖于底层的具体事务管理框架,比如,如果底层的事务管理框架是全局的(例如JTA、或者是CMT),那么Spring的事务管理就是全局的,如果底层的事务管理是局部的(例如:Hibernate、JDBC,JDO等),则Spring的事务管理就是局部的。在很多情况下,即使使用的是EJB的CMT管理多个数据源(数据库),可能仍然会选择使用Spring事务管理应用,因为Spring提供一种声明式的事务管理机制,在对已有代码进行零改动(使用XML+AOP)或者略微改动(使用Annotation+AOP)的情况下,统一管理事务,因此,有利于代码在不同的事务策略和运行环境中迁移。
Spring 事务源码(4.3.7.RELEASE)
Spring的事务管理通过org.springframework.transaction.PlatformTransactionManager接口表示:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
PlatformTransactionManager是一个服务提供商接口(SPI),针对不同类型的底层事务框架,Spring提供了不同的PlatFormTransactionManager实现版本,在使用过程中选择适当的实现版本管理底层事务框架即可,常见的有:
- JDBC org.springframework.jdbc.datasource.DataSourceTransactionManager
- JTA org.springframework.transaction.jta.JtaTransactionManager
- Hibernate org.springframework.orm.hibernate3.HibernateTransactionManager
下面分析PlatFormTransactionManager中的方法,getTransaction方法,通过TransactionDefinition传入参数返回TransactionStatus对象。TransactionStatus可能表示一个新的事务,或者表示一个已经存在的事务(如果当前线程的调用栈中已经存在一个事务)。在JavaEE中经常将事务与一个具体的执行线程相关联,因此,在获取事务时,如果当前线程中已经存在事务,即将存在的事务返回,否则创建一个新事务返回。
TransactionDefinition接口规范
- Isolation:事务隔离等级(是数据库事务的一个概念,不同数据库支持不同的事务隔离等级)
- Propagation:事务传播方式,通过设置可以决定,当一个事务方法准备执行前,线程栈中已经存在一个事务,可选的策略为:使用先前的事务作为即将执行的方法事务,或者,将先前的事务暂停,创建一个全新的事务处理当前方法,处理结束后,在返回先前的事务。
- Timeout:如果事务持续的时间超时,则启动底层事务管理的回滚机制,回滚当前事务。
- Read-only status:Read-only事务主要应用于代码中仅读取数据,并未对数据修改的情况。在某些情况下,Read-only事务可以优化事务管理,例如底层事务框架使用Hibernate时。
TransactionStatus提供了简单的方式控制事务执行和查询事务状态
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
hiberante 作为底层事务框架
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
...
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
在txManager中指定HibernateTransactionManager作为底层事务实现. 在txManager中指定Hibernate Sessionfactory。在Hibernate中通过Sessionfactory中的OpenSession方法创建Session,之后使用Session中的beginTransaction方法开启事务。将sessionFactory注入到HibernateTransactionManager中的属性后,Spring即可通过这个sessionFactory控制Hibernate事务管理。
所以,Spring事务管理仍然是依赖于底层的事务管理框架,而Spring只是提供对事务的一种抽象,这种抽象能够在多种事务框架间迁移。当需要新的事务管理框架和策略支持时,无需更改代码,只需更改配置文件即可完成这种事务框架间的迁移。
编程式事务
Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。
使用TransactionTemplate(线程安全)
使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。
TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
Object result = tt.execute(new TransactionCallback(){
public Object doTransaction(TransactionStatus status){
updateOperation();
return resultOfUpdateOperation();
}
}); // 执行execute方法进行事务管理
使用PlatformTransactionManager
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定义一个某个框架平台的TransactionManager,如JDBC,Hibernate
dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
try {
// 数据库操作 之间的差别 - 月光雨的专栏 - CSDN博客 之间的差别 - 月光雨的专栏 - CSDN博客
dataSourceTransactionManager.commit(status);// 提交
} catch (Exception e) {
dataSourceTransactionManager.rollback(status);// 回滚
}
声明式事务管理
声明式事务管理和 EJB的CMT主要区别
- CMT依赖于JTA,只适用于全局事务管理,Spring不单可以适用于JTA,同时也适用于局部事务管理(例如:JDBC,JPA,Hibernate和JDO等)。
- Spring声明式事务管理可以应用于任意的类管理,而EJBs只是用于特殊类管理。
- Spring的回滚规则与EJB不同。
- Spring可以通过AOP规则在回滚过程中插入用户定义行为,也可以同事务管理advice一起定义任意的advice。(advice是AOP的概念)
- Spring不支持跨越多个远程调用的事务管理。一般认为Spring的事务最多在一个请求范围内完成,如果需要跨越多个请求过程管理事务,那么应当选择EJB,但是这种情况并不多见。
回滚规则的制定十分重要。当然,可以使用传统的方式,调用TransactionStatus中的setRollback方法,通过代码回滚事务;但是,更常用的是定义回滚规则,当应用程序执行过程中满足了某项回滚规则(定义制定Runtime exception出发事务回滚),当前事务应当自动回滚。通过Spring的声明式事务管理,无需在代码中加入任何与事务相关的内容,即可完成按回滚规则管理事务的方式。Spring与EJB相似,仅对未处理的运行时异常(runtime exception)回滚。
声明式事务实现机制
Spring通过元数据(metadata,包括XML或者annotation)配置声明式事务管理, 并使用AOP代理机制最终实现事务管理。
通过元数据标明需要事务管理的方法(通过AOP中的cut-point),同时提供事务管理的策略advice(通过TX的advice),并结合实际提供的PlatformTransactionManager。当用户调用了事务管理方法时,系统使用具体的PlatformTransactionManager实现,根据advice提供的策略创建(或者获取)一个事务,之后执行方法内部逻辑,当出现异常时,回滚当前事务,否则执行结束,提交事务,清理当前事务。整个过程是使用AOP代理完成的,AOP代理在方法代码前添加事务启动逻辑,在方法执行后添加事务提交和清理逻辑,并且代理监控方法中可能出现的异常,当有未捕获异常抛出时,代理使用相应的回滚逻辑回滚事务。
XML事务配置默认设置
< tx:advice> 标签可以用于声明事务的配置,默认设置为
- propagation 默认是Required
- Isolation 默认是Default
- Transaction 默认是read/write
- timeout 默认-1
- rollback-for
- no-rollback-for
Annotation配置事务, @Transactional
<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- a PlatformTransactionManager is still required -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
可以使用@Transactional标记一个接口,接口中的方法,一个类的定义或者类中的公共方法。但是仅仅提供@Transactional标记是不够的,@Transactional标记只是一种能够被某种运行时架构使用的配置元数据,因此需要提供能够自动识别@Transactional标记的架构支持。上例中,使用< tx:annotation-driven/>元素开启了标记的事务行为。
< tx:annotation-driven/>的配置
- transaction-manager,默认transactionManager,事务管理器的名字,仅当事务管理器不是transactionManager时,需要显示指定。
- mode,默认proxy,proxy模式仅当外部调用产生时才产生代理。可替代的选项是aspectj,通过更改类的字节码来提供事务功能的织入。aspectJ织入需要提供spring-aspects.jar包支持。
- proxy-target-class,默认为false。只适用于proxy方式,用于控制代理创建的方式。当值为true时,基于class类型的代理将被创建,否则,基于标准的JDK接口的代理将被创建。
- order,默认为Orderd.LOWEST_PRECEDENCE,定义多个advice执行的次序,默认情况下由系统决定。
Notices:
- < tx:annotation-driven/> 默认使用代理模式, 仅有在公有方法上标记的@Transactional是有效的,所有的私有的、受保护的或者包可见性的方法即使标记了@Transactional也不会有实质性的事务管理行为产生,并且系统不会给出任何错误或者提示信息。如果有必要在非公有方法上标记事务,那么不应当使用代理模式的事务管理,可以考虑使用AspectJ。
- Spring提倡将@Transactional标记在类(或者类的方法上),不提倡对接口(或者接口方法进行标记)。在接口或者接口方法上进行@Transactional标记是可行的,但是仅有系统运行在基于接口的代理前提下事务管理才会发生。实际上Java标记不会通过接口继承,这意味着如果你使用基于类的代理(prox-target-calss=”ture”)或者使用weaving-based aspact(mode=”aspectj”),这样在接口上的标记将不会被代理或者织入架构识别,并且对象不会被事务代理包装,最终无法实现事务管理。
- 在代理模式中(是Spring事务管理默认使用的),仅有外部方法调用过程才会被代理截获,这意味着自身调用,即一个方法调用了本对象的另外一个方法不会导致一个实质的事务管理代理过程产生,即使是被调用的方法标记了@Transactional。
@Transactional设置
- value 类型String,可选的属性,用于指定事务管理者的名字(Transaction Manager)。
- propagation 类型enum:Propagation。可选的,事务传播行为。
- isolation 类型enum:Isolation。可选的,事务隔离级别。
- timeout 类型int。事务失效时间。
- readonly类型boolean。read/write或者read-only事务设置。
- rollbackeFor 类型是Throwable子类的数组(对象)。设置出发rollback异常事件。
- rollbackForClassname,类型是Throwable子类的数组(类名)。
- noRollbackFor
- noRollbackForClassname
Spring 事务传播(propagation)
在理解事务传播之前,需要先理解物理(physical)事务和逻辑(logical)事务。物理事务是一个真正的事务,而逻辑事务是某段代码内部的事务处理(例如一个内嵌方法内部)。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRED事务传播方式中,会为每一个方法创建一个逻辑事务。每一个内部逻辑事务可以独立于外部逻辑事务单独设置rollback-only的状态,但是所有的逻辑事务最终都会映射为一个物理事务。所以,内部逻辑事务产生的rollback-only状态将会直接影响到外部逻辑事务的实质提交。
当内部逻辑事务设置rollback-only时,而外部事务仍然没有决定事务是否应该回滚(因为内部设置回滚时,外部并不知道内部已经设置了),这个回滚对外部来说是不可预期的。因此需要在内部抛出一个UnexpectedRollbackException的异常,根据这个预期的行为,外部事务会获得内部事务并没有按照预期的方式执行的通知。如果一个内部的逻辑事务无声的标记了rollback-only,但是外部并有感知到内部的这一变化,那么外部调用者将会继续执行提交。因此,外部调用者需要获得一个UnexpectedRollbackException异常,以便清楚的确定内部事务已经回滚,不要在继续执行提交任务。
PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW与PROPAGATON_REQUIRED不同,对每一个逻辑事务使用一个完整的物理事务,在这种情况下,底层的物理事务是不同的,因此可以独立的提交和回滚,因此内部的事务回滚状态不会影响到外部事务。内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作, 看外层事务是否对该异常进行回滚.
PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。使用一个物理事务管理多个检查点,事务可以回滚到某个检查点。这种部分回滚策略,允许内部事务回滚自己处理范围,即使内部事务回滚,外部事务依然可以继续执行提交。这种设置只适用于JDBC(DataSourceTransactionManager中设置有事务管理的检查点)。
事务嵌套demo
外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B(), ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED.
ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:
- 捕获异常,执行异常分支逻辑
java
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
}
这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。 - 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback
事务隔离
数据库隔离级别
http://write.blog.csdn.net/postlist
- Read-Uncommitted 0 导致脏读
- Read-Committed 1 避免脏读,允许不可重复读和幻读
- Repeatable-Read 2 避免脏读,不可重复读,允许幻读
- Serializable 3 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle
少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB
Spring 事务隔离
- ISOLATION_DEFAULT 这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。
- ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
- ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
- ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
- ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。