Spring事务管理
思维导图
简述
什么是事务?事务有哪些作用?为什么要使用事务?事务有哪些特性?接下来,这些问题我一个都不会回答,我依旧把相关准备知识的文章放在文末以供大家学习参考。
大家都知道,SSM是Spring、Spring MVC和Mybatis的整合框架,在基于SSM的Java EE应用程序开发中,一般情况下都会使用Spring来进行事务管理。虽然Mybatis作为一个持久化框架有它自己的一套事务管理机制,但是一般都用在单独使用Mybatis时才会用到。
我们在前一篇文章中已经分析了Mybatis的事务管理,因为我们不在SSM中采用,所以只是对他的一些核心接口进行了分析和对比。在这一篇中,我们会使用最基础的SSM框架,来逐步添加Spring事务处理所需的配置和进行相应的编码,同时对Spring事务管理的知识点进行总结归纳。要是想最快的,最简单的实现事务管理,请点开目录,直接到基于@Transactional小节,非常暴力的在十分钟内实现事务管理。
Spring事务属性
Spring中,事务的一些操作定义于spring-dao-x.x.x.jar的TransactionDefinition接口中,该接口的一些方法及属性如下:
package org.springframework.transaction;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
接口中提供了一些获取属性的方法,而对属性的设置方法则由开发者自行定义,保存属性的字段没有任何要求,但是必须保证Spring在进行事务操作时,通过以上接口提供的方法必须能够返回事务的相关属性值。
事务隔离级别
也就是IsolationLevel。可以看到在上述接口中的5个Isolation前缀的属性,他们分别为Spring的五种隔离级别。
隔离级别属性 | 代表含义 |
---|---|
ISOLATION_DEFAULT | 默认值,表示使用底层数据库的默认隔离级别。这通常取决于你所使用的数据库,而对于大多数的数据库,默认的值通常取TransactionDefinition.ISOLATION_READ_COMMITTED。 |
ISOLATION_READ_UNCOMMITTED | 未提交读, 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。此隔离级别不能防止脏读和不可重复读,因此很少使用该隔离级别。 |
ISOLATION_READ_COMMITTED | 已提交读,该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。 |
ISOLATION_REPEATABLE_READ | 可重复读,该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。 |
ISOLATION_SERIALIZABLE | 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 |
事务传播行为
事务的传播行为是指如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。说的直白点就是你现在所要执行的事务方法已经在一个事务中时你做出的行为,就好比你在占领地盘的时候,已经有人接管了这块地盘,你会怎么做的问题。
事务传播行为属性 | 行为 |
---|---|
PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常。 |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价PROPAGATION_REQUIRED。 |
(其实和黑老大抢地盘做出的行为有很多的类似之处)。
在这七种传播行为中,前6种都是从EJB中引入的,和EJB中的传播行为概念相同。最后一个时Spring独有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,内部事务的提交只能通过外部的事务提交来引发而不能单独提交。另外外部事务的回滚也会导致嵌套子事务的回滚。
事务超时
一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition中以int类型定义,单位是秒。
只读
指定对事务性资源(被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等)进行只读操作或者是读写操作。boolean类型。
回滚规则
一般而言,如果在事务中抛出了未检查异常,则事务将回滚。如果没有抛出任何异常或者抛出的异常已被检查,则仍然提交事务(EJB 中的默认处理方式)。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务(^_^)。
支持的策略
传统Java EE有两种策略:全局事务和局部事务。在使用Spring的事务管理策略时,可以有效的解耦,开发者在切换不同的事务策略时,无需修改代码,只需要改变相应的相应的配置。
本地事务 –基于JDBC事务管理器
他和所采用的持久化技术有关,不同的持久化技术有不同的对象来完成事务操作。
全局事务 –基于JTA事务管理器
全局事务需要服务器的底层JTA支持,从JNDI中获取数据源,然后进行全局事务管理(什么是JNDI?请戳,JNDI如何配置?请戳)。JTA的事务管理在企业应用中用的相对较多,我们再此不做深层的研究,在后续的博客中再做详细的解释和应用,请谅解!
管理方式
编程式事务管理
编程式事务管理需要显式的编写事务管理的代码,例如调用一些所使用持久化框架中提交事务或回滚事务的一些方法。Spring提供了事务管理的API,可以在应用代码中使用,而在底层,Spring仍将事务操作的技术交给对应的持久化框架来完成。
基于底层API
Spring的事务管理有三个核心接口,分别为PlatformTransactionManager,TransactionDefinition 和TransactionStatus。我们来分析以下:
... ...
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
这三个方法均和平台无关。他和所有的事务策略都分离开来,随着底层不同事务策略的切换,他会采用不同的实现类,并且无需和任何的事务性资源进行绑定。所以他可以应用与任何事务策略(在使用时可以结合Ioc来注入相关的平台特性)。采用不同配置,Spring会自动匹配相应的实现类,将实务操作任务交给对应的持久化框架来完成。
接下来上一段使用编程式事务管理的例子来感受下(编程式事务管理建议单独使用JDBC配合Spring来写一个Demo而不是在SSM框架上,我会在文末提供案例):
public class UserServiceImpl{
private IUserDao userDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
//省略getter和setter
public int addUser(User user)
{
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
//这里的result是在插入用户时所插入的数目,若为0,则插入失败
int result=0;
try {
result= userDao.insertSelective(user);
txManager.commit(txStatus);
} catch (Exception e) {
result = 0;
txManager.rollback(txStatus);
System.out.println("插入失败");
}
return result;
}
}
配置:
<bean id="userService" class="com.javafeng.service.impl_by_config.UserServiceImpl">
<property name="userDao" ref="IUserDao"/>
<property name="txManager" ref="transactionManager"/>
<property name="txDefinition">
<bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
<!--事务传播行为-->
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
</property>
</bean>
如上,在代码中添加TransactionDefinition对象来定义一个事务,PlatformTransactionManager对象用于执行事务操作。如果某个方法需要进行事务管理操作,需要调用PlatformTransactionManager对象的getTransaction(…)方法启动一个事务,创建并启动事务后,进行逻辑代码的编写,然后在适当的位置添加提交事务(commit(…)方法)和回滚事务(rollback(…)方法)的代码。
基于TransactionTemplate
基于底层API的方式非常好理解,但是随着需要进行事务管理的地方越来越多,每个模块都会有类似的事务管理的代码存在,代码就会变得越来越繁杂,耦合度很高,极不利于后期维护。因此,Spring提供了一种简化的方法,即模板回调模式。上代码:
public class UserServiceImpl {
private IUserDao userDao;
private TransactionTemplate transactionTemplate;
//省略getter和setter
public int addUser(final User user)
{
//执行事务获取事务的执行结果对象
return (int) transactionTemplate.execute(new TransactionCallback(){
public Object doInTransaction(TransactionStatus status) {
Object result;
try {
result = userDao.insertSelective(user);
} catch (Exception e) {
status.setRollbackOnly();
result = false;
System.out.println("插入错误!");
}
return result;
}
});
}
}
配置:
<bean id="userService" class="com.javafeng.service.impl_by_config.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
<property name="transactionTemplate">
<bean class="org.springframework.transaction.support.TransactionTemplate">
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
</property>
</bean>
TransactionTemplate 的 execute() 方法有一个 TransactionCallback 类型的参数,该接口中定义了 doInTransaction() 方法,我们以匿名内部类(可以戳我)的方式实现 TransactionCallback 接口,然后在doInTransaction()中编写逻辑代码。采用这种形式时可以采用默认的提交和回滚规则,在逻辑代码中无需显式调用任何事务管理的API,要想回滚此事务,可以在doInTransaction()中调用其TransactionStatus 参数的setRollbackOnly()方法,设置事务为回滚的,来让事务进行回滚。
此时事务行为:
条件 | 行为 |
---|---|
显式调用TransacationStatus.setRollbackOnly() | 回滚事务 |
执行回调方法的过程中抛出了未检查异常 | 回滚事务 |
事务执行完成 | 提交事务 |
抛出了checked类型异常 | 提交事务 |
TransactionCallback 接口有一个子接口 TransactionCallbackWithoutResult,该接口中定义了一个 doInTransactionWithoutResult() 方法。对于不需要返回值的情况我们就有两种选择:
- 使用 TransactionCallback 接口,并在方法中返回任意值。
- 使用TransactionCallbackWithoutResult 接口。代码如下:
public void addUser(final User user)
{
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
public void doInTransactionWithoutResult(TransactionStatus status) {
try {
userDao.insertSelective(user);
} catch (Exception e) {
status.setRollbackOnly();
System.out.println("插入错误!");
}
}
});
}
声明式事务管理
区别于编程式事务管理,它不需要在代码中显示的调用有关事务处理的代码只需在配置文件中相关的声明或者使用注解的方式来实现。
本质上它对方法前后进行拦截,使目标方法执行之前常见或加入事务,在目标方法执行后根据情况回滚或提交事务。这样就形成了一个横切逻辑,因此可以将AOP运用于此。当然Spring开发者也已经做足了相应的工作。
我们在后续的开发中,使用的正是声明式事务管理,并且推荐大家在以后开发中使用。
当然它也有缺点,那就是它进行事务管理最小的单位是方法,而不是代码块。当然这个问题可以通过将需要进行事务管理的代码提取为一个单独的方法。
基于 TransactionInter
从代码入手进行分析:
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="userServiceTarget"
class="com.javafeng.service.impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<bean id="userService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userServiceTarget"/>
<property name="interceptorNames">
<list>
<idref bean="transactionInterceptor"/>
</list>
</property>
</bean>
我们来分析一下这个配置:
- 配置一个 TransactionInterceptor
它主要来定义相关的事务规则。它的transactionManager属性用来指定一个事务管理器并且将相关事务操作委托给它;另一个是Properties 类型的transactionAttributes 属性,用来定义事务规则,key就是方法名,可以使用通配符,值表示该方法所应用的事务属性。事务属性的取值规则如下
传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常]
- 其中隔离级别和传播行为的取值应和上面的两个表中的属性值完全相同,例如传播行为PROPAGATION_MANDATORY和隔离级别ISOLATION_DEFAULT。
- 如果事务是只读的,使用“readOnly”指定只读属性。否则不需要设置该属性。
- 超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
- 不影响提交的异常:即使事务中抛出了这些类型的异常,事务也会正常提交。须在每一个异常的名字前面加上“+”,异常的名字可以只是类名的一部分,例如“+Exception”,“+ption”。
- 导致回滚的异常:事务中抛出这些异常时事务回滚。须在每一个异常的名字前面加上“-”。同样,异常的名字可以只是类名的一部分,例如“-Exception”,“-ption”。
拓展一下上面的配置:
<prop key="*User">PROPAGATION_REQUIRED,readOnly,ISOLATION_READ_COMMITTED,TIMEOUT_10,+AException,-BException,-CException</prop>
如上表示事务管理针对所有结尾是User的方法,传播行为为PROPAGATION_REQUIRED,事务为只读,隔离级别是已提交读ISOLATION_READ_COMMITTED,超时时间10s,事务抛出AException异常时正常提交,抛出BException异常和CException时事务回滚。
2. 配置一个 ProxyFactoryBean 来组装 target 和advice。这是一个Spring的AOP行为。
缺点:配置太多。
必须为每一个目标对象配置一个 ProxyFactoryBean,导致在有很多业务类时出现大量的配置。为了解决这个问题,就出现了下面得基于 TransactionProxy的方式。
基于 TransactionProxy
Spring提供的TransactionProxyFactoryBean将TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一。来看一下配置:
<bean id="bankServiceTarget"
class="com.javafeng.service.impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<bean id="bankService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="bankServiceTarget"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
基于 <tx> 命名空间
在前两种方式的基础上,Spring 2.x 中开始引入 <tx> 命名空间,配合支持切点表达式的<aop> 命名空间,实现了基于 <tx> 命名空间的声明式事务管理。同样分析配置:
<!--此处我们使用JDBC数据源的局部事务,注意需要依赖注入DataSource-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource" />
</bean>
<!--业务逻辑Bean-->
<bean id="userService"
class="com.javafeng.service.impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<!--事务增强-->
<tx:advice id="userAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="user" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="userPointcut" expression="execution(* *.user(..))"/>
<aop:advisor advice-ref="userAdvice" pointcut-ref="user Pointcut"/>
</aop:config>
若使用默认的事务属性,可简化为:
<!--此处我们使用JDBC数据源的局部事务,注意需要依赖注入DataSource-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource" />
</bean>
<!--业务逻辑Bean-->
<bean id="userService"
class="com.javafeng.service.impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<!--事务增强-->
<tx:advice id="userAdvice" transaction-manager="transactionManager" />
<aop:config>
<aop:pointcut id="userPointcut" expression="execution(**.User(..))"/>
<aop:advisor advice-ref="userAdvice" pointcut-ref="userPointcut"/>
</aop:config>
<tx:advice />用来配置事务增强管理,其中的<tx:attributes />元素用来指定事务的属性,在<tx:attributes />内可以定义多个<method />子元素来批量的为不同的方法指定不同的事务属性,支持通配符的使用。
此处使用了<aop>命名空间和切点表达式。不再需要每一个业务类都创建一个代理对象。
<aop:advisor >可以将<Advice>所配置的事务增强和切入点(通过pointcut-ref指定已有切入点或通过<aop:pointcut>来指定切点表达式)绑定,从而将<Advice>所配置的事务增强织入到相应的切入点。
如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。
使用这种声明式事务管理时,还可以定义多个<tx:advice>和多个切入点,来分别为不同的业务逻辑方法指定不同的事务属性。
<method>属性,其中某些属性与上节的分析类似,可进行参考:
属性 | 作用 |
---|---|
name | 需要使用事务管理的方法名。支持通配符。如“list*”。 |
isolation | 指定事务隔离级别。默认isolation.DEFAULT |
propagation | 指定事务传播行为。默认propagation.REQUIRED |
timeout | 指定事务超时时间。单位秒,默认值-1(代表不超时) |
read-only | 指定事务是否为只读。默认false |
rollback-for | 指定引起事务回滚的异常。使用全限定类名,可指定多个并用“,”隔开(半角逗号)。 |
no-rollback-for | 指定不引起事务回滚的异常。使用全限定类名,可指定多个并用“,”隔开(半角逗号)。 |
基于 @Transactional
基于 @Transactional的方式是最简单,最容易上手的一种事务实现方式,它也是Spring 2.x引入的基于注解(Annotation)的方式。他的作用范围为接口,接口方法,类,类方法。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,如果再方法级别使用该注解会覆盖类级别的事务属性定义。看一下配置和代码:
@Transactional(propagation = Propagation.REQUIRED)
public void insertSelective(User user) {
userDao.insertSelective(user);
}
配置(启动事务注解扫描):
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
与上相似,transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字为该值,则可以省略该属性。可以通过设置@Transactional的属性来赋予相应的事务处理属性。
@Transactional具体属性如下:
属性 | 作用 |
---|---|
isolation | 指定事务隔离级别。默认为底层持久化框架的事务隔离级别 |
propagation | 指定事务传播行为 |
readOnly | 指定事务是否为只读 |
timeout | 指定事务超时时间 |
noRollbackFor | 指定抛出或捕获特定的异常时强制不回滚事务 |
noRollbackForClassName | 指定抛出或捕获多个特定的异常时强制不回滚事务。可以指定多个异常类名 |
RollbackFor | 指定抛出或捕获特定的异常时强制回滚事务 |
RollbackForClassName | 指定抛出或捕获特定的异常时强制回滚事务。可以指定多个异常类名 |
注意点:
- @Transactional 注解用于接口或接口方法时,只有在使用基于接口的代理时它才会生效(有实现类)。
- @Transactional 注解应该只能被应用到 public 方法上。这是由AOP本质决定的。如果在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,都将被忽略,而且不会抛出任何异常。
优劣对比
方式 | 优点 | 缺点 |
---|---|---|
基于TransactionInterceptor | 通过对它学习有利于对底层实现更好的理解 | 配置相对复杂 |
基于TransactionProxyFactoryBean | 通过对它学习有利于对底层实现更好的理解 | 配置相对复杂 |
基于<tx>命名空间 | 与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法 | 较基于 @Transactional方式而言配置较复杂 |
基于 @Transactional | 简单明了,配置简单,简单学习直接上手 | 必须在每一个需要使用事务的方法或者类上用 @Transactional注解,切无法重用 |
原理
分层 | 内容 |
---|---|
接口层 | 通过PlatformTransactionManager、TransactionDefinition、TransactionStatus接口向上层应用提供事务处理控制方法。其中TransactionDefination接口定义事务各种属性(包括各种传播行为和隔离级别以及其他);TransactionStatus保存事务执行状态;PlatformTransactionManager是向上层应用提供事务处理方法的接口。 |
实现层 | 提供实现类。主要包括:AbstractPlatformTransactionManager、DefaultTransactionDefinition、DefaultTransactionStatus、SuspendedResourceHolder、TransactionSynchronizationManager等。AbstractPlatformTransactionManager、DefaultTransactionDefinition、DefaultTransactionStatus分别实现了PlatformTransactionManager、TransactionDefinition、TransactionStatus接口。 |
适配层 | 继承实现AbstractPlatformTransactionManager类等,集成各种访问数据源或者消息中间件的类库。见上思维导图。 |
参考资料
Mybatis的environments和properties
以上链接版权属作者所有,在此不一一鸣谢!