之前对spring的事务传播机制没有概念,花点时间去看了事务的源码,以及这些事务传播机制使用的文档,在此做一下简单的笔记
正文
下面说提到的共享事务的意思就是几个service共用同一个事务,如传播机制Propagation.REQUIRED
从源码看AOP如何实现事务
我们想使用事务,那就得配置spring元数据,配置事务管理器以及aop的事务的切面,当然可以在spring的xml配置文件中配置,也可以使用注解,其结果是一样的。
在aop的切面中,配置了切点,IOC在读取元数据信息,进而装配,最后将元数据实例化缓存到IOC容器的过程中,根据配置的切面,符合切点的所有的类,将会代理创建实例对象,将它缓存到IOC容器中。打个比方,事务的织入一般应用在service层,那么我们获取IOC中的某个service对象,其实已经是动态代理创建的对象。
具体请看文章《spring的IOC与AOP读源码小记》第二部分
这里面userService与logonService就是动态代理创建的对象
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void register(RegisterDTO dto) {
userService.addUser(dto);
logonService.addLogon(dto);
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
上面的代码,如logonService.addLogon执行的时候到底做了些什么?
在logonService.addLogon这个代码上加入了一个断点,我们F5进去,发现进入了invoke方法中,这说明了是使用了jdk动态代理创建的对象。
在invoke中执行了下面关键的操作
获取事务的拦截器链,这里也就是获取了为TransactionInterceptor的增强
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
- 1
- 1
转移到ReflectiveMethodInvocation这个类中处理。也就是需要依赖这个类的功能协助完成事务织入
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed();
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
在proceed中,很有意思,我觉得这里就像是一个递归,先执行TransactionInterceptor事务的织入,在执行原目标的方法得到结果
递归的结构如: A -> B -> A 拦截器链执行完了,就执行原目标方法跳出递归
先执行TransactionIntercetor事务织入,看看TransactionInterceptor是如何处理吧
public Object invoke(final MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}. // The TransactionAttributeSource should be passed the target class // as well as the method, which may be from an interface. Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { @Override public Object proceedWithInvocation() throws Throwable { return invocation.proceed(); } }); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这里invocation.proceed() 回调函数就是跳回到ReflectiveMethodInvocation中执行,由于事务拦截器只有一个,所以执行的是所代理的类的被调用的方法,返回结果
invokeWithinTransaction是织入事务,有三个比较重要的处理方法
开启事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
- 1
- 1
执行被代理的service的调用的方法。下面方法就是执行上面所说的invocation.proceed,回去执行service调用的方法
retVal = invocation.proceedWithInvocation();
- 1
- 1
如果出现异常,那么在这个方法中回滚,这个方法默认如果是检查型异常则提交,如果RuntimeException 和 error则回滚
completeTransactionAfterThrowing(txInfo, ex);
- 1
- 1
这里执行了代码的提交
commitTransactionAfterReturning(txInfo);
- 1
- 1
下面主要是从这三个方法中去讲述
从源码中看传播机制如何创建事务
开启事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
- 1
- 1
在里面有一个getTransaction方法
1、某一个service执行的时候,发现当前已经存在一个事务了,那么它执行下面的代码逻辑,根据传播机制选择在handleExistingTransaction中处理,是否要重新创建事务,还是使用原来的事务
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
我们看看handleExistingTransaction如何处理的
①、PROPAGATION_NEVER,非事务运行,存在事务就报错
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
②、PROPAGATION_NOT_SUPPORTED,非事务运行,如果有事务则挂起
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus( definition, null, false, newSynchronization, debugEnabled, suspendedResources); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
③、PROPAGATION_REQUIRES_NEW,如果当前有事务则挂起,创建新的事务,没有事务则创建新事务。
代码就不贴了,太长了,反正handleExistingTransaction处理的就是存在事务了,这些传播机制是如何处理的。
2、如果没有存在事务,那么传播机制为PROPAGATION_MANDATORY,抛出异常,如果是PROPAGATION_REQUIRED这些就创建新事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { doBegin(transaction, definition); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
doBegin是HibernateTransactionManager的方法,它去开启一个事物
从源码看多个事务回滚
completeTransactionAfterThrowing(txInfo, ex)这个方法中去回滚
1、在这个方法里面有txInfo.transactionAttribute.rollbackOn(ex)判断,如果是RuntimeException和Error的异常,则回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
- 1
- 1
这个rollback对于不同的传播机制,处理不同
有savepoint就回滚到保存的回滚点,这个是NESTED的传播机制处理
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
如果这个status是新事务,则滚回。每一个service调用的方法,织入事务,都有一个状态。如果是共享事物的service,除了是事务创建最外层的service,它的isNewTransaction是true,其它所共享事务的service的status状态的isNewTransaction为false
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status); }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
打个比方
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void register(RegisterDTO dto) {
userService.addUser(dto);
logonService.addLogon(dto);
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
register方法是创建一个新的事物,它是最外层的事物边界,userService与logonService的传播机制为REQUIRED,它们相同register创建的事物,它们的status的isNewTransaction为false,
从上面看,只有到了register才真正的回滚
那么userService与logonService需要回滚做什么呢?
存在事务,说明是共享事物了,那么就标记rollback-only,到最外层的时候一起回滚,如userService和logonService只是表示成rollback-only,抛出的异常在register的事务织入中捕捉,并真正在register中回滚,它的isNewTransaction为true
else if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); } doSetRollbackOnly(status); } else { if (status.isDebug()) { logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
注意的地方:从上面看共享事物,不是最外层的边界的service,其他的标记成rollback-only,这就要求这些service所抛出的异常,不能try catch 捕获,不然的话,在register,发现没有从这些共享service抛出异常,直接提交,从而抛出rollback-only的异常
具体请看文章《Spring事务异常rollback-only》
2、如果是检查型异常则提交
想检查型异常,出错了,被捕获,但是不会去回滚,直接提交了。事务注解里面或者配置文件里面可以对这些异常做处理从而处理回滚
如果事务
最后说一下,不管是什么事务传播机制,这些service嵌套了以后,最外层的service总能捕获某一个service抛出的RuntimeException异常,从而回滚。比如register中嵌套了userService和logonService,
不过userService和logonService是什么传播机制类型,里面抛出的运行时异常,总能在register事务织入catch到的,从而也一起回滚了
。logonService 是requires_new 那么里面抛出的异常就在register中try掉,让userService的处理能够顺利完成。但是try catch使用也要小心,如果是共享事务的service,try-catch,那么就会出现rollback-only异常。这些都是看源码明白的,实战是不是这样处理我还真不知道,没用过
从源码看多个事务的提交
commitTransactionAfterReturning,这里是提交,在这个方法中通过下面代码处理的
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
- 1
- 1
里面commit代码
public final void commit(TransactionStatus status) throws TransactionException { if (status.isCompleted()) { throw new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback more than once per transaction"); } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; if (defStatus.isLocalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Transactional code has requested rollback"); } processRollback(defStatus); return; } if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); } processRollback(defStatus); // Throw UnexpectedRollbackException only at outermost transaction boundary // or if explicitly asked to. if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } return; } processCommit(defStatus); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
下面这段代码如果发现是全局rollback-only标识在,那么说明之前共享事物某个service已经出现异常了,判断是不是该共享事物的最外层service,如果是,rollback回滚,并抛出rollback-only,没错,抛出异常,为什么呢,因为rollback-only为true,最外层不应该在commitTransactionAfterReturning处理的,而是completeTransactionAfterThrowing处理
如果不是最外层,那么继续标记成rollback-only,在最外层以后再做处理,我看最外层还是抛rollback-only,因为之前共享事物,已经有rollback-only为true了,那么为什么会在commitTransactionAfterReturning处理,我看是在register代码中就try cacth掉那个异常,从而没有捕获,进入completeTransactionAfterThrowing处理异常
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
- 1
- 1
processCommit方法中处理
从代码上看,对有savepoint的处理
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint"); } status.releaseHeldSavepoint(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
对当前有isNewTransaction为true的处理,直接提交,如果是共享事务最外层边界,直接提交
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
doCommit(status); }
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
如果是共享事务,不是最外层,提交跳过了,不会提交,在最外层边界的service执行事务的时候才提交
- spring的管理的事务可以分为如下2类:
- 逻辑事务 在spring中定义的事务通常指逻辑事务,提供比物理事务更抽象,方便的事务配置管理,但也基于物理事务
- 物理事务 特定于数据库的事务
- spring中支持一下2中事务声明方式
- 编程式事务 当系统需要明确的,细粒度的控制各个事务的边界,应选择编程式事务
- 声明式事务 当系统对于事务的控制粒度较粗时,应该选择申明式事务
- 无论你选择上述何种事务方式去实现事务控制,spring都提供基于门面设计模式的事务管理器供选择,如下是spring事务中支持的事务管理器
-
事务管理器实现(org.springframework.*) 使用时机 jdbc.datasource.DataSourceTransactionManager 使用jdbc的抽象以及ibatis支持 orm.hibernate.HibernateTransactionManager 使用hibernate支持(默认3.0以下版本) orm.hibernate3.HibernateTransactionManager 使用hibernate3支持 transaction.jta.JtaTransactionManager 使用分布式事务(分布式数据库支持) orm.jpa.JpaTransactionManager 使用jpa做为持久化工具 orm.toplink.TopLinkTransactionManager 使用TopLink持久化工具 orm.jdo.JdoTransactionManager 使用Jdo持久化工具 jms.connection.JmsTransactionManager 使用JMS 1.1+ jms.connection.JmsTransactionManager102 使用JMS 1.0.2 transaction.jta.OC4JJtaTransactionManager 使用oracle的OC4J JEE容器 transaction.jta.WebLogicJtaTransactionManager 在weblogic中使用分布式数据库 jca.cci.connection.CciLocalTransactionManager 使用jrping对J2EE Connector Architecture (JCA)和Common Client Interface (CCI)的支持
UML结构图如下
4、各种事务管理器的定义如下
-
- JdbcTransactionManager定义如下
-
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"/>
- </bean>
- hibernate事务管理器配置如下
-
- <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
- <property name="sessionFactory" ref="sessionFactory"/><span style="white-space: pre;"> </span>
- </bean>
- hibernate的事务管理器会注入session会话工厂,然后将事务处理委托给当前的transaction对象,事务提交时,调用commit()方法,回滚时调用rollback()方法
- jpa事务管理器配置如下
-
- <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
- <span style="white-space: pre;"> </span><property name="entityManagerFactory" ref="entityManagerFactory"/>
- </bean>
- 其余事务管理器可参见spring in action中说明
- 5、申明式事务配置
- spring特有的事务传播行为,spring支持7种事务传播行为,确定客户端和被调用端的事务边界(说得通俗一点就是多个具有事务控制的service的相互调用时所形成的复杂的事务边界控制)下图所示为7钟事务传播机制
-
传播行为 含义 PROPAGATION_REQUIRED(XML文件中为REQUIRED) 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚) PROPAGATION_SUPPORTS(XML文件中为SUPPORTS) 表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行 PROPAGATION_MANDATORY(XML文件中为MANDATORY) 表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常 PROPAGATION_NESTED(XML文件中为NESTED) 表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同PROPAGATION_REQUIRED的一样 PROPAGATION_NEVER(XML文件中为NEVER) 表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常 PROPAGATION_REQUIRES_NEW(XML文件中为REQUIRES_NEW) 表示当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。 PROPAGATION_NOT_SUPPORTED(XML文件中为NOT_SUPPORTED) 表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行
- 6、spring中的事务隔离级别
- spring的事务隔离级别其实本质上是对SQL92标准的4种事务隔离级别的一种封装,具体参加我的博文:http://kingj.iteye.com/admin/blogs/1675011
- spring的事务隔离级别如下表所示
-
隔离级别 含义 ISOLATION_DEFAULT 使用数据库默认的事务隔离级别 ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的修改,可能导致脏读、幻读和不可重复读 ISOLATION_READ_COMMITTED 允许从已经提交的事务读取,可防止脏读、但幻读,不可重复读仍然有可能发生 ISOLATION_REPEATABLE_READ 对相同字段的多次读取的结果是一致的,除非数据被当前事务自生修改。可防止脏读和不可重复读,但幻读仍有可能发生 ISOLATION_SERIALIZABLE 完全服从ACID隔离原则,确保不发生脏读、不可重复读、和幻读,但执行效率最低。
- 7、spring事务只读属性
- spring事务只读的含义是指,如果后端数据库发现当前事务为只读事务,那么就会进行一系列的优化措施。它是在后端数据库进行实施的,因此,只有对于那些有可能启动一个新事务的传播行为(REQUIRED,REQUIRES_NEW,NESTED)的方法来说,才有意义。(测试表明,当使用JDBC事务管理器并设置当前事务为只读时,并不能发生预期的效果,即能执行删除,更新,插入操作)
- 8、spring的事务超时
- 有的时候为了系统中关键部分的性能问题,它的事务执行时间应该尽可能的短。因此可以给这些事务设置超时时间,以秒为单位。我们知道事务的开始往往都会发生数据库的表锁或者被数据库优化为行锁,如果允许时间过长,那么这些数据会一直被锁定,影响系统的并发性。
- 因为超时时钟是在事务开始的时候启动,因此只有对于那些有可能启动新事物的传播行为(REQUIRED,REQUIRES_NEW,NESTED)的方法来说,事务超时才有意义。
- 9、事务回滚规则
- spring中可以指定当方法执行并抛出异常的时候,哪些异常回滚事务,哪些异常不回滚事务。
- 默认情况下,只在方法抛出运行时异常的时候才回滚(runtime exception)。而在出现受阻异常(checked exception)时不回滚事务,这个ejb的回滚行为一致。
- 当然可以采用申明的方式指定哪些受阻异常像运行时异常那样指定事务回滚。
- 10、spring申明式事务配置
- 将aop,tx命名空间添加到当前的spring配置文件头中
- 定义一个事务AOP通知
-
- <tx:advice id="txAdvice" transactionManager="transactionManager">
- <tx:attributes>
- <tx:method name="add*" propagation="REQUIRED"/>
- </tx:attributes>
- </tx:advice><span style="white-space: pre;"> </span>
- 定义一个事务切面,即应该在哪些类的哪些方法上面进行事务切入
-
- <aop:config>
- <aop:advisor pointcut="execution(* *..zx.spring.UserService*.*(..))||execution(* *..spring.ServiceFacade.*(..))||execution(* *..spring.BookService.*(..))" advice-ref="txAdvice"/>
- </aop:config>
- 11、结合具体的代码实现讲解spring的7种事务传播机制效果
- 准备环境
- 我们采用JDBC+ORACLE实现具体操作,首先搭建好spring的开发环境,配置好数据源,建立好Test
- 创建数据库结构,创建一个用户表和Book表
-
- CREATE TABLE T_USER(
- ID INT,
- NAME VARCHAR2(200)
- );
- CREATE TABLE T_BOOK(
- ID INT ,
- NAME VARCHAR2(200)
- );
- 搭建好的结构如下
-
- <!-- 定义数据源 -->
- <bean id="ams" class="com.mchange.v2.c3p0.ComboPooledDataSource"
- destroy-method="close">
- <property name="driverClass" value="${jdbc.ams.driver}" />
- <property name="jdbcUrl" value="${jdbc.ams.url}" />
- <property name="user" value="${jdbc.ams.username}" />
- <property name="password" value="${jdbc.ams.password}" />
- <property name="initialPoolSize" value="${initialSize}" />
- <property name="minPoolSize" value="${minPoolSize}" />
- <property name="maxPoolSize" value="${maxActive}" />
- <property name="acquireIncrement" value="${acquireIncrement}" />
- <property name="maxIdleTime" value="${maxIdleTime}" />
- </bean>
- <!-- 定义jdbc模板类-->
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="ams"/>
- </bean>
- 在spring包下面建立3个service
- BookService
-
- package com.zx.spring;
- import org.springframework.jdbc.core.JdbcTemplate;
- public class BookService {
- public static final String ADD_BOOK="insert into t_book(id,name) values(1,'duck-j2ee')";
- private JdbcTemplate jdbcTemplate;
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RollbackException("跳出执行");
- }
- public JdbcTemplate getJdbcTemplate() {
- return jdbcTemplate;
- }
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- }
- UserService
-
- package com.zx.spring;
- import org.springframework.jdbc.core.JdbcTemplate;
- public class UserService {
- public static final String ADD_USER="insert into t_user(id,name) values(1,'duck')";
- private BookService bs;
- private JdbcTemplate jdbcTemplate;
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
- public JdbcTemplate getJdbcTemplate() {
- return jdbcTemplate;
- }
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- public BookService getBs() {
- return bs;
- }
- public void setBs(BookService bs) {
- this.bs = bs;
- }
- }
- 创建一个ServiceFacade门面,将UserService和BookService包装起来
-
- package com.zx.spring;
- public class ServiceFacade {
- private BookService bs;
- private UserService us;
- public BookService getBs() {
- return bs;
- }
- public void setBs(BookService bs) {
- this.bs = bs;
- }
- public UserService getUs() {
- return us;
- }
- public void setUs(UserService us) {
- this.us = us;
- }
- public void addUserBook()throws Exception{
- bs.addBook();
- us.addUser();
- }
- }
- 上面我们配置了3个service接口,并在spring中配置了申明式事务。
-
- <aop:config>
- <aop:advisor pointcut="execution(* *..zx.spring.UserService*.*(..))||execution(* *..spring.ServiceFacade.*(..))||execution(* *..spring.BookService.*(..))" advice-ref="txAdvice"/>
- </aop:config>
- 我们在一个pointcut中定义了3个aspectj方式的切入点,即对这3个类的所有方法进行事务切入。
接下来我们开始配置不同的事务传播机制,来看看效果。 - 到此,准备工作大功告成,接下来我们来对7中传播机制做一个详细解释。
- 一 :我们来对单个方法的事务传播机制进行一个了解
- REQUIRED, REQUIRES_NEW
- junit代码
-
- @Test
- public void testAddBook()throws Exception{
- BookService bs=(BookService)this.getBean("bookService");
- bs.addBook();
- }
- addBook()代码
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException("throw runtime exception in outter transaction");
- }
- 执行junit后,控制台如下
-
- 1、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 2、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 3、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@a2b392]
- 可以知道,当addBook()方法的事务传播机制为REQUIRED, REQUIRES_NEW ,并且抛出运行时异常时,将会回滚事务。
- 当addBook()方法抛出受检查的异常时,将不会回滚事务。
- addBook()方法如下:
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new Exception("throw runtime exception in outter transaction");
- }
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.BookService.addBook]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 3、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 4、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@4310d0]
- 第3行可知,事务提交,不回滚
- MANDATORY
- 代码同上所示,只是事务传播机制改为 MANDATORY
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" />
- <tx:method name="addBook" propagation="MANDATORY"/>
- </tx:attributes>
- </tx:advice>
- 运行junit后,由于单个方法执行没有指定任何事务传播机制,因此抛出异常。
-
- org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
- at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:357)
- NESTED
- NESTED内嵌事务,如果没有外层事务,则新建一个事务,行为同REQUIRED一样
- NEVER
- NEVER不会以事务方式运行,执行junit代码后,控制台如下
-
- 1、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 2、[DEBUG,DataSourceUtils,main] Registering transaction synchronization for JDBC Connection
- 3、[DEBUG,DataSourceTransactionManager,main] Should roll back transaction but cannot - no transaction available
- 由于发生运行时异常,事务本应该回滚,但是在第三行可以知道,由于事务传播机制为NEVER,因此找不到事务进行回滚,数据库只添加了一条记录。
- SUPPORTS
- 单个方法 调用时supports行为同NEVER一样,不会创建事务,只是如果有事务,则会加入到当前事务中去,具体的行为下面有分析。
- NOT_SUPPORTED
- 单个方法被执行时,不会创建事务。如果当前有事务,将封装事务挂起,知道该方法执行完成再恢复封装事务,继续执行。
- REQUIRED, REQUIRES_NEW
- 1、我们将UserService和BookService中的addUser(),addBook()方法分别配置为REQUIRED,并在addBook()方法中抛出运行时异常。
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED"/>
- <tx:method name="addBook" propagation="REQUIRED"/>
- </tx:attributes>
- </tx:advice>
-
- 1、public void addUser()throws Exception {
- 2、 this.bs.addBook();
- 3、 this.jdbcTemplate.execute(ADD_USER);
- }
-
- 4、public void addBook() throws Exception{
- 5、 this.jdbcTemplate.execute(ADD_BOOK);
- 6、 throw new RuntimeException("跳出执行");
- }
- 测试用例如下
-
- @Test
- public void testAddUser()throws Exception{
- UserService us=(UserService)this.getBean("userService");
- us.addUser();
- }
- 执行前数据库如下
- 执行后,console控制台输出如下:
-
- [DEBUG,DataSourceTransactionManager,main]
- Creating new transaction with name [com.zx.spring.UserService.addUser]:
- PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 我们再观察控制台输出
-
- 1、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] for JDBC transaction
- 2、[DEBUG,DataSourceTransactionManager,main] Switching JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] to manual commit
- 3、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 4、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 5、[DEBUG,DataSourceTransactionManager,main] Participating transaction failed - marking existing transaction as rollback-only
- 6、[DEBUG,DataSourceTransactionManager,main] Setting JDBC transaction [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] rollback-only
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5]
- 第一行jdbc事务管理器从c3p0连接池中获取一个链接
- 第二行设置jdbc事务提交方式为手动提交
- 代码执行到方法addUser()中第一行时,由于addUser()方法的事务级别为REQUIRED的因此,事务管理器开始了一个事务。执行到第二行addBook()时,由于addBook()方法的事务传播行为为REQUIRED的,我们知道REQUIRED方式是如果有一个事务,则加入事务中,如果没有,则新建一个事务。由控制台输出的第3行可以知道,addBook方法加入到了addUser方法的事务当中去,接着第4行执行了插入t_book语句,由于addBook()方法在第6行时,抛出了运行时异常,因此当前事务失败,可从控制台输出第5行得知。
- 由于addUser()和addBook()方法共享了一个事务,在addBook()方法中又抛出了运行时异常,因此事务必须回滚,这由数据库查询可知。
- 如果我们将addBook()方法中抛出的运行时异常改为checked异常的话,会是什么结果呢?
-
- 7、public void addBook() throws Exception{
- 8、 this.jdbcTemplate.execute(ADD_BOOK);
- 9、 throw new Exception("跳出执行");
- }
-
- 9、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] for JDBC transaction
- 10、[DEBUG,DataSourceTransactionManager,main] Switching JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1973fc5] to manual commit
- 11、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 12、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 13、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 14、[DEBUG,DataSourceTransactionManager,main] Releasing JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@457d21] after transaction
- 由控制台输出第13行可以知道,除了addBook()方法中抛出的检查异常被忽略之外,其它的同上面的一致。再看数据库可以知道,addBook()方法和被执行了,addUser()方法被抛出的检查异常终止调用。
- 如果我们给addUser()方法指定rollback-for属性,那么addBook()方法的事务回回滚吗?
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new Exception("跳出执行");
- }
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" <span style="color: #ff0000;"><span style="color: #888888;">rollback-for="Exception"</span></span>/>
- <tx:method name="addBook" propagation="REQUIRED"/>
- </tx:attributes>
- </tx:advice>
- 控制台输出如下
-
- 15、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 16、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 17、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 18、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@121d383]
- 2、上面我们讨论了两个方法都指定为事务传播机制为REQUIRED,那么我们来改变以下addBook()方的事务传播机制改为NEVER ,来看看它们的效果
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" />
- <tx:method name="addBook" propagation="NEVER"/>
- </tx:attributes>
- </tx:advice>
-
- org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
- at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:399)
- at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:347)
- at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
- 3、我们接着将addBook()方法的事务传播机制改为MANDATORY
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" />
- <tx:method name="addBook" propagation="MANDATORY"/>
- </tx:attributes>
- </tx:advice>
-
- [DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- [DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- [DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,'duck')]
- [DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
-
- 4、我们将addBook()方法的事务传播机制改为NESTED-内嵌事务,那么传播机制之间会怎么互相影响呢?
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" />
- <tx:method name="addBook" propagation="NESTED"/>
- </tx:attributes>
- </tx:advice>
- addBook()方法如下,依然抛出(checked-exception)检查异常。
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException("跳出执行");
- }
- addUser()方法如下,在方法体中捕获addBook()抛出的异常。如果不捕获异常,addUser()方法将会被终止。
- public void addUser()throws Exception {
- try {
- this.bs.addBook();
- }catch(Exception e) {
- e.printStackTrace();
- }
- this.jdbcTemplate.execute(ADD_USER);
- }
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Creating nested transaction with name [com.zx.spring.BookService.addBook]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 4、[DEBUG,DataSourceTransactionManager,main] Rolling back transaction to savepoint
- 5、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,'duck')]
- 6、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 7、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@10e164e]
- 由上面的输出可以,第一行为执行addUser()方法,事务管理器开始了一个事务,
- 第二行执行到addBook()方法,由于addBook()方法的事务传播机制为NESTED内嵌事务,因此,开始一个新的事务。
- 第三行可以知道插入语句,由于addBook()方法内部抛出RuntimeException,因此内部嵌套事务回滚到外层事务创建的保存点。
- 注意这个地方,我们抛出的是运行时异常,如果我们抛出受检查的异常,那么spring会默认的忽略此异常。下面我会详细阐述。
- 如果内层事务抛出检查异常,那么外层事务将忽略此异常,但是会产生一个问题。那就是:外层事务使用jdbc的保存点API来实现嵌套事务,
- 但是数据库不一定支持。我做测试的是oracle数据库,jdbc事务管理器在内层事务抛出检查异常后,将会在内层事务结束后,释放外层事务
- 创建的保存点,这是时候数据库不一定支持。因此可能会抛出如下异常:
-
- java.sql.SQLException: 不支持的特性
- at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)
- at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:146)
- at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:208)
- 第五行可以知道,外层事务开始执行,第六行可知外层事务提交。
- 总结可知:对于NESTED内层事务而言,内层事务独立于外层事务,可以独立递交或者回滚。
- 如果我们在addUser方法内部抛出一个运行时异常,那么会怎么样呢?
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- throw new RuntimeException("throw runtime exception in outter transaction");
- }
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- }
- 执行junit后,控制台输入如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Creating nested transaction with name [com.zx.spring.BookService.addBook]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 4、[DEBUG,DataSourceTransactionManager,main] Releasing transaction savepoint
- 5、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,'duck')]
- 6、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 7、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1622a94]
- 第一行在addUser()方法执行后,事务管理器创建一个新的事务。
- 第二上和上面一样,由于addBook()方法是NETSTED的内嵌传播机制,因此新建一个事务。
- 执行插入,释放保存点。
- 执行插入t_user插入,但是此时抛出了一个运行时异常,外层事务回滚,那么内层事务是否回滚呢?我们看以下数据库记录。
- t_user表数据为空,事务回滚了
t_book表数据也为空,证明内层事务回滚了
- 由上述结果可知,如果对于一个内嵌事务来说,外层事务的回滚必将导致内层事务回滚。
-
- 5、我们再将addBook()方法的事务传播机制该为REQUIRES_NEW,来看看会有什么有趣的事情发生?
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" />
- <tx:method name="addBook" propagation="REQUIRES_NEW"/>
- </tx:attributes>
- </tx:advice>
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- //throw new RuntimeException("throw runtime exception in outter transaction");
- }
- 执行junit后,控制台输出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 4、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 5、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@5b96c2]
- 由上可知,第一行执行addUser()方法创建一个事务,
- 第二行阻塞addUser()方法,并创建一个新的事务,执行插入t_book表,提交内层事务和外层事务。
- 或许有的读者会问,在下面addUser()方法中由于第一行和第二行是顺序执行,因此不能说明说明问题,那么我们将addUser()方法中的1、2行代码调换,在看效果:
-
- public void addUser()throws Exception {
- 1、 this.bs.addBook();
- 2、 this.jdbcTemplate.execute(ADD_USER);
- }
- 兑换后的代码
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- this.bs.addBook();
- }
- 在来看控制台输出
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,'duck')]
- 3、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 4、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 5、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 6、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1b7ae22]
- 7、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 8、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 9、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1fa681c]
- 由第一、二行可知道正在执行插入t_user表操作,而到第3行中我们可以知道,插入t_user表的事务被挂起,并且新建了一个事务来插入t_book表
- t_book表插入事务提交后,到第7行可知,前一个事务t_user插入操作被恢复,并提交前一个操作。
-
- 如果我们在addBook()方法中抛出运行时异常,来看看会有什么有趣的事情发生?
- addBook()方法代码如下
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException("throw runtime exception in outter transaction");
- }
- addUser()方法代码如下
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- this.bs.addBook();
- }
- 执行junit后,控制台输出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,'duck')]
- 3、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 4、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 5、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 6、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@10d0b72]
- 7、[DEBUG,DataSourceTransactionManager,main] Releasing JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@10d0b72] after transaction
- 8、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction8、
- 9、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 10、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@44b361]
- 11、[DEBUG,DataSourceTransactionManager,main] Releasing JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@44b361] after transaction
- 第一行可知,执行addUser()方法时,从链接池获取一个新链接,创建一个封装事务,执行t_user表插入。
- 第三行可知,t_user插入事务被挂起,一直到第7行,插入t_book表事务被回滚
- 第8行可知,t_user事务恢复,但是此时该封装事务被回滚。我们再看数据库.
- t_user表数据和t_book表数据均为空
- 由此我们可以知道,对于REQUIRES_NEW事务传播机制,如果被调用端抛出运行时异常,则被调用端事务回滚,那么调用端的事务到底是回滚还是提交呢?
- 如果调用段代码捕获了被调用端抛出的运行时异常,那么调用端事务提交,不回滚
- 我们将addUser()调用端代码该成如下(捕获addBook()抛出的运行时异常)
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- try {
- this.bs.addBook();
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- 执行junit后,控制台信息如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1bcec19] for JDBC transaction
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,'duck')]
- 4、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction, creating new transaction with name [com.zx.spring.BookService.addBook]
- 5、[DEBUG,DataSourceTransactionManager,main] Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@ba507b] for JDBC transaction
- 6、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@ba507b]
- 9、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 10、java.lang.RuntimeException: throw runtime exception in outter transaction
- at com.zx.spring.BookService.addBook(BookService.java:11)
- 11、[DEBUG,DataSourceTransactionManager,main] Initiating transaction commit
- 12、[DEBUG,DataSourceTransactionManager,main] Committing JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1bcec19]
- 由上面的输出可以知道,1-3行从连接池获取一个链接,开始执行插入事务
- 执行addBook()方法时,因其事务传播属性为REQUIRES_NEW,则将上一个事务阻塞
- 第6-8行可知,addBook()方法抛出运行时异常,新事务被回滚
- 第9行恢复执行上一个插入t_user表事务,并捕获到addBook()抛出的异常,自此addUser()方法未抛出任何运行时异常,提交事务。
- 如果调用端未捕获被调用端抛出的运行时异常,那么调用端事务回滚,不提交
- 我们将addUser()方法调用端改成如下(不捕获addBook()抛出的运行时异常,直接抛出)
-
- public void addUser()throws Exception {
- this.jdbcTemplate.execute(ADD_USER);
- this.bs.addBook();
- }
- 执行junit后,控制台输出如下:
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_user(id,name) values(1,'duck')]
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 4、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 5、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@63a721]
- 6、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1706da8]
- 由上述第1-5行可知,插入t_user表事务被挂起,同时插入t_book事务被回滚,因为抛出了运行时异常。
- 6-8行插入t_user事务被回滚,因为addUser()方法的事务传播界别为REQUIRED,因此在抛出了运行时异常的情况下,会回滚事务。
- 那么,为什么会造成上面两种截然不同的结果呢?因为addUser()方法被声明为REQUIRED传播机制,只要它抛出运行时异常,均会回滚事务。
- 如果调用段代码捕获了被调用端抛出的运行时异常,那么调用端事务提交,不回滚
- 6、 我们再将addBook()方法的事务传播机制该为SUPPORTS,来看看会有什么有趣的事情发生?
- 将addBook()方法的事务机制改为SUPPORTS
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" />
- <tx:method name="addBook" propagation="SUPPORTS"/>
- </tx:attributes>
- </tx:advice>
- addUser()方法
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
- addBook()方法
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException("throw runtime exception in outter transaction");
- }
- 执行junit后,控制台输出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Participating in existing transaction
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 4、[DEBUG,DataSourceTransactionManager,main] Participating transaction failed - marking existing transaction as rollback-only
- 5、[DEBUG,DataSourceTransactionManager,main] Setting JDBC transaction [com.mchange.v2.c3p0.impl.NewProxyConnection@19e09a4] rollback-only
- 6、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 7、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@19e09a4]
- 由第二行可知,addBook()方法的加入到了当前addUser()方法的事务中,第4行可知,addBook()方法抛出运行时异常,此时addUser()方法的事务被标记为rollback,整个事务都
- 将回滚。如果addUser()方法没有任何事务,那么addBook()方法也不会在事务环境中执行。不管是否抛出异常,sql都将执行。
- 如果addBook()方法抛出受检查的异常,那么此异常将忽略,整个addUser()方法的事务将提交。addBook()方法也不会尝试去回滚事务
-
- 7、 我们再将addBook()方法的事务传播机制该为NOT_SUPPORTED,会怎么样呢?
- 将addBook()方法的事务机制该为NOT_SUPPORTED
-
- <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- <tx:method name="addUser" propagation="REQUIRED" />
- <tx:method name="addBook" propagation="NOT_SUPPORTED"/>
- </tx:attributes>
- </tx:advice>
- addBook()方法
-
- public void addBook() throws Exception{
- this.jdbcTemplate.execute(ADD_BOOK);
- throw new RuntimeException("throw runtime exception in outter transaction");
- }
- addUser()方法
-
- public void addUser()throws Exception {
- this.bs.addBook();
- this.jdbcTemplate.execute(ADD_USER);
- }
- 执行junit后,控制台输出如下
-
- 1、[DEBUG,DataSourceTransactionManager,main] Creating new transaction with name [com.zx.spring.UserService.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 2、[DEBUG,DataSourceTransactionManager,main] Suspending current transaction
- 3、[DEBUG,JdbcTemplate,main] Executing SQL statement [insert into t_book(id,name) values(1,'duck-j2ee')]
- 4、[DEBUG,DataSourceUtils,main] Fetching JDBC Connection from DataSource
- 5、[DEBUG,DataSourceTransactionManager,main] Should roll back transaction but cannot - no transaction available
- 6、[DEBUG,DataSourceTransactionManager,main] Resuming suspended transaction after completion of inner transaction
- 7、[DEBUG,DataSourceTransactionManager,main] Initiating transaction rollback
- 8、[DEBUG,DataSourceTransactionManager,main] Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@137d4a4]
- 由上可知,第二行是将addUser()方法的事务挂起,开始执行addBook()代码,
- 第5行可知,addBook()方法抛出运行时异常,需要回滚事务,但是又没有事务来回滚,因此t_book表数据被插入
- 由于addBook()抛出异常,在addUser()方法中未捕获该异常,因此对addUser()方法的事务传播机制REQUIRED来说,抛出了运行时异常,addUser()方法回滚
- 如果将addUser()方法该为如下:
-
- public void addUser()throws Exception {
- try {
- this.bs.addBook();
- }catch(Exception e) {
- e.printStackTrace();
- }
- this.jdbcTemplate.execute(ADD_USER);
- }
- 那么addUser()方法将会提交,addBook()方法将插入一条数据到t_book表中,
- 如果addBook()方法抛出了受检查异常,addBook()方法将忽略此异常,不尝试任何事务回滚,同样即使在addUser()方法中不捕获addBook()方法抛出的受检查异常,addUser()方法也会提交事务,而忽略此异常。
- 7、 我们再将addBook()方法的事务传播机制该为NOT_SUPPORTED,会怎么样呢?