下面是官方的解释
/**
* Execute within a nested transaction if a current transaction exists,
* behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
* Note: Actual creation of a nested transaction will only work on specific
* transaction managers. Out of the box, this only applies to the JDBC
* DataSourceTransactionManager when working on a JDBC 3.0 driver.
* Some JTA providers might support nested transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
如果事务存在则在一个嵌套的事务中执行,如果没有则像PROPAGATION_REQUIRED表现形式一样,支持嵌套事务的前提条件
*JDBC3+
* 使用DataSourceTransactionManager 事务管理器
spring配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="checkoutTimeout" value="30000" />
<property name="idleConnectionTestPeriod" value="30" />
<property name="maxIdleTime" value="30" />
<property name="initialPoolSize" value="10" />
<property name="minPoolSize" value="10" />
<property name="maxPoolSize" value="20" />
<property name="acquireIncrement" value="5" />
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
beans>
测试代码
package org.bear.bookstore.test.propagation.jdbc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class Nested {
@Autowired JdbcTemplate jdbcTemplate;
@Transactional(
propagation=Propagation.REQUIRED,
rollbackFor={Exception.class}
)
public void a(){
jdbcTemplate.execute("insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','1')");
int x = 1/0;
System.out.println(x);
}
@Transactional(
propagation=Propagation.NESTED,
rollbackFor={Exception.class}
)
public void b(){
jdbcTemplate.execute("insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','0')");
int x = 1/0;
System.out.println(x);
}
}
package org.bear.bookstore.test.propagation.jdbc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class Nested2 {
@Autowired Nested nested;
@Autowired JdbcTemplate jdbcTemplate;
@Transactional(
propagation=Propagation.REQUIRED,
rollbackFor={Exception.class}
)
public void a(){
/**
* 如果aa()抛出异常,b未出现异常,则a会回滚b的结果
*/
aa();
try {
/**
* 调用b,捕获异常,异常不会冒泡,aa()执行成功
* 也就是nested的用法,在一个类中这么调用没有效果,如调用aaa()
*/
nested.b();
/**
* 如果调用a则会出现如下异常,
* org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
at org.bear.bookstore.test.propagation.jdbc.Nested2$$EnhancerBySpringCGLIB$$567ca65b.a()
at org.bear.bookstore.test.propagation.JdbcTransactionPropagationTest.NestedTest(JdbcTransactionPropagationTest.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
*/
//nested.a();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(
propagation=Propagation.REQUIRED,
rollbackFor={Exception.class}
)
public void aa(){
jdbcTemplate.execute("insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','1')");
/*int x = 1/0;
System.out.println(x);*/
}
/**
* 这样的结果是
* 如果bbb是propagation=Propagation.NESTED,则两个custom都保存成功
* 如果bbb是propagation=Propagation.REQUIRED,则两个custom都保存成功
*/
@Transactional(
propagation=Propagation.REQUIRED,
rollbackFor={Exception.class}
)
public void aaa(){
jdbcTemplate.execute("insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','1')");
try {
bbb();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(
//propagation=Propagation.NESTED,
propagation=Propagation.REQUIRED,
rollbackFor={Exception.class}
)
public void bbb(){
jdbcTemplate.execute("insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','1')");
int x = 1/0;
System.out.println(x);
}
}
package org.bear.bookstore.test.propagation;
import org.bear.bookstore.test.propagation.jdbc.Nested2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
locations={"file:src/test/resources/spring-app-jdbc.xml","file:src/test/resources/spring-jdbc.xml"}
)
public class JdbcTransactionPropagationTest {
/**
* 运行在datasourcemanager中
*/
@Autowired Nested2 Nested;
/**
* PROPAGATION_NESTED
*
*/
@Test
public void NestedTest(){
Nested.a();
//Nested.aaa();
}
}
咱们看堆栈信息
Thread [main] (Suspended (breakpoint at line 186 in DataSourceTransactionManager))
DataSourceTransactionManager.doGetTransaction() line: 186
DataSourceTransactionManager(AbstractPlatformTransactionManager).getTransaction(TransactionDefinition) line: 337
TransactionInterceptor(TransactionAspectSupport).createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String) line: 447
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class>, InvocationCallback) line: 277
TransactionInterceptor.invoke(MethodInvocation) line: 96
CglibAopProxy$CglibMethodInvocation(ReflectiveMethodInvocation).proceed() line: 179
CglibAopProxy$DynamicAdvisedInterceptor.intercept(Object, Method, Object[], MethodProxy) line: 655
Nested$$EnhancerBySpringCGLIB$$cb68a86.b() line: not available
Nested2.a() line: 28
Nested2$$FastClassBySpringCGLIB$$7a769380.invoke(int, Object, Object[]) line: not available
MethodProxy.invoke(Object, Object[]) line: 204
CglibAopProxy$CglibMethodInvocation.invokeJoinpoint() line: 720
CglibAopProxy$CglibMethodInvocation(ReflectiveMethodInvocation).proceed() line: 157
TransactionInterceptor$1.proceedWithInvocation() line: 99
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class>, InvocationCallback) line: 282
TransactionInterceptor.invoke(MethodInvocation) line: 96
CglibAopProxy$CglibMethodInvocation(ReflectiveMethodInvocation).proceed() line: 179
CglibAopProxy$DynamicAdvisedInterceptor.intercept(Object, Method, Object[], MethodProxy) line: 655
Nested2$$EnhancerBySpringCGLIB$$d54acf0.a() line: not available
JdbcTransactionPropagationTest.NestedTest() line: 25
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available
Method.invoke(Object, Object...) line: not available
FrameworkMethod$1.runReflectiveCall() line: 50
FrameworkMethod$1(ReflectiveCallable).run() line: 12
FrameworkMethod.invokeExplosively(Object, Object...) line: 47
InvokeMethod.evaluate() line: 17
RunBeforeTestMethodCallbacks.evaluate() line: 75
RunAfterTestMethodCallbacks.evaluate() line: 86
SpringRepeat.evaluate() line: 84
SpringJUnit4ClassRunner(ParentRunner).runLeaf(Statement, Description, RunNotifier) line: 325
SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 252
SpringJUnit4ClassRunner.runChild(Object, RunNotifier) line: 94
ParentRunner$3.run() line: 290
ParentRunner$1.schedule(Runnable) line: 71
SpringJUnit4ClassRunner(ParentRunner).runChildren(RunNotifier) line: 288
ParentRunner.access$000(ParentRunner, RunNotifier) line: 58
ParentRunner$2.evaluate() line: 268
RunBeforeTestClassCallbacks.evaluate() line: 61
RunAfterTestClassCallbacks.evaluate() line: 70
SpringJUnit4ClassRunner(ParentRunner).run(RunNotifier) line: 363
SpringJUnit4ClassRunner.run(RunNotifier) line: 191
JUnit4TestReference.run(TestExecution) line: 86
TestExecution.run(ITestReference[]) line: 38
RemoteTestRunner.runTests(String[], String, TestExecution) line: 459
RemoteTestRunner.runTests(TestExecution) line: 678
RemoteTestRunner.run() line: 382
RemoteTestRunner.main(String[]) line: 192
具体分析流程
重点日志打印信息
2016-11-23 11:03:26.652 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate at 430 - Executing SQL statement [insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','1')]
2016-11-23 11:04:09.012 [main] DEBUG o.s.jdbc.datasource.DataSourceTransactionManager at 450 - Creating nested transaction with name [org.bear.bookstore.test.propagation.jdbc.Nested.b]
2016-11-23 11:04:09.102 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate at 430 - Executing SQL statement [insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','0')]
2016-11-23 11:04:09.104 [main] DEBUG o.s.jdbc.datasource.DataSourceTransactionManager at 845 - Rolling back transaction to savepoint
java.lang.ArithmeticException: / by zero
at org.bear.bookstore.test.propagation.jdbc.Nested.b(Nested.java:30)
at org.bear.bookstore.test.propagation.jdbc.Nested$$FastClassBySpringCGLIB$$c1e2caf2.invoke()
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
at org.bear.bookstore.test.propagation.jdbc.Nested$$EnhancerBySpringCGLIB$$cb68a86.b()
at org.bear.bookstore.test.propagation.jdbc.Nested2.a(Nested2.java:28)
at org.bear.bookstore.test.propagation.jdbc.Nested2$$FastClassBySpringCGLIB$$7a769380.invoke()
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
at org.bear.bookstore.test.propagation.jdbc.Nested2$$EnhancerBySpringCGLIB$$d54acf0.a()
at org.bear.bookstore.test.propagation.JdbcTransactionPropagationTest.NestedTest(JdbcTransactionPropagationTest.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)2016-11-23 11:04:09.102 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate at 430 - Executing SQL statement [insert into custom(address,cusname,email,phone,sex) values('北京市,朝阳区,十里河村','xxx','[email protected]','15555555555','0')]
2016-11-23 11:04:09.111 [main] DEBUG o.s.jdbc.datasource.DataSourceTransactionManager at 759 - Initiating transaction commit
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
2016-11-23 11:04:09.104 [main] DEBUG o.s.jdbc.datasource.DataSourceTransactionManager at 845 - Rolling back transaction to savepoint
从堆栈信息和打印信息我们可以清楚的得到以下信息:
测试类方法调用
JdbcTransactionPropagationTest.NestedTest() line: 25
CBLIB字节码增强我的service类
Nested2$$EnhancerBySpringCGLIB$$d54acf0.a() line: not available
事务拦截器拦截注解了事务的方法
Nested2.a() line: 28
Nested2$$FastClassBySpringCGLIB$$7a769380.invoke(int, Object, Object[]) line: not available
MethodProxy.invoke(Object, Object[]) line: 204
CglibAopProxy$CglibMethodInvocation.invokeJoinpoint() line: 720
CglibAopProxy$CglibMethodInvocation(ReflectiveMethodInvocation).proceed() line: 157
TransactionInterceptor$1.proceedWithInvocation() line: 99
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class>, InvocationCallback) line: 282
TransactionInterceptor.invoke(MethodInvocation) line: 96
CglibAopProxy$CglibMethodInvocation(ReflectiveMethodInvocation).proceed() line: 179
CglibAopProxy$DynamicAdvisedInterceptor.intercept(Object, Method, Object[], MethodProxy) line: 655
发现调用了另一个service的声明了NESTED的方法,创建savepoint,走新的代理拦截流程
Nested$$EnhancerBySpringCGLIB$$cb68a86.b() line: not available
现在我们解释一些问题了
为什么b的执行失败也不会影响aa方法的插入操作,因为b失败后事务,会回滚到我们保存的savepoint,(当然咱们必须捕获异常进行处理)然后继续往下执行,也就是执行a方法的其它流程
为什么aa的执行失败会影响b,理由同上
不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之。
不断的学习,不断的交流,不断的实践