事务之六:spring嵌套事务⼀、基本概念
事务的隔离级别,事务传播⾏为见《》
⼆、嵌套事务⽰例
2.1、Propagation.REQUIRED+Propagation.REQUIRES_NEW
package dxz.demo1;
@Service
public class ServiceAImpl implements ServiceA {
@Autowired
private ServiceB serviceB;
@Autowired
private VcSettleMainMapper vcSettleMainMapper;
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
String id = atePayId("A");
VcSettleMain vc = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("ServiceAImpl VcSettleMain111:" + vc);
VcSettleMain vc2 = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc2);
System.out.println("ServiceAImpl VcSettleMain22222:" + vc2);
}
private VcSettleMain buildModel(String id) {
VcSettleMain vc = new VcSettleMain();
vc.setBatchNo(id);
vc.setCreateBy("dxz");
vc.w());
vc.setTotalCount(11L);
vc.setTotalMoney(BigDecimal.ZERO);
vc.setState("5");
return vc;
}
}
ServiceB
package dxz.demo1;
@Service
public class ServiceBImpl implements ServiceB {
@Autowired
private VcSettleMainMapper vcSettleMainMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public void methodB() {
String id = atePayId("B");
VcSettleMain vc = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("---ServiceBImpl VcSettleMain:" + vc);
}
}
controller
package dxz.demo1;
@RestController
@RequestMapping("/dxzdemo1")
@Api(value = "Demo1", description="Demo1")
public class Demo1 {
@Autowired
private ServiceA serviceA;
/**
* 嵌套事务测试
*/
@PostMapping(value = "/test1")
public String methodA() throws Exception {
return "ok";
}
}
结果:
看数据库表记录:
这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW,ServiceB是⼀个独⽴的事务,与外层事务没有任何关系。如果ServiceB 执⾏失败(上⾯⽰例中让ServiceB的id为已经存在的值),ServiceA的调⽤出会抛出异常,导致ServiceA的事务回滚。
并且,在 ServiceB#methodB 执⾏时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本⽂的讨论范围)。
2.2、Propagation.REQUIRED+Propagation.REQUIRED
//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
//ServiceB
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodB(String id) {
//...
--“1”可插⼊,“2”可插⼊,“3”不可插⼊:
结果是“1”,“2”,“3”都不能插⼊,“1”,“2”被回滚。
--“1”可插⼊,“2”不可插⼊,“3”可插⼊:
结果是“1”,“2”,“3”都不能插⼊,“1”,“2”被回滚。
2.3、Propagation.REQUIRED+⽆事务注解
//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
//ServiceB
//...
@Override
//没有加事务注解
public void methodB(String id) {
/
/...
--“1”可插⼊,“2”可插⼊,“3”不可插⼊:
结果是“1”,“2”,“3”都不能插⼊,“1”,“2”被回滚。
2.4、内层事务被try-catch:
2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
try {
} catch (Exception e) {
System.out.println("内层事务出错啦。");
}
}
//ServiceB
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodB(String id) {
//...
-
-“1”可插⼊,“2”不可插⼊,“3”可插⼊:
结果是“1”,“2”,“3”都不能插⼊,“1”被回滚。
事务设置为Propagation.REQUIRED时,如果内层⽅法抛出Exception,外层⽅法中捕获Exception但是并没有继续向外抛出,最后出现“Transaction rolled back because it has been marked as rollback-only”的错误。外层的⽅法也将会回滚。
其原因是:内层⽅法抛异常返回时,transacation被设置为rollback-only了,但是外层⽅法将异常消化掉,没有继续向外抛,那么外层⽅法正常结束时,transaction 会执⾏commit操作,但是transaction已经被设置为rollback-only了。所以,出现“Transaction rolled back because it has been marked as rollback-only”错误。
2.4.2、trycatch+Propagation.REQUIRED+Propagation.NESTED
//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
try {
} catch (Exception e) {
System.out.println("内层事务出错啦。");
}
}
//ServiceB
//...
@Override
@Transactional(propagation = Propagation.NESTED, readOnly = false)
public void methodB(String id) {
//...
--“1”可插⼊,“2”不可插⼊,“3”可插⼊:
结果是“1”,“3"记录插⼊成功,“2”记录插⼊失败。
说明:
当内层配置成 PROPAGATION_NESTED, 此时两者之间⼜将如何协作呢? 从 Juergen Hoeller 的原话中我们可以到答案, ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执⾏前的 SavePoint(注意, 这是本⽂中第⼀次提到它, 潜套事务中最核⼼的概念), ⽽外部事务(即 ServiceA#methodA) 可以有以下两种处理⽅式:
1、内层失败,外层调⽤其它分⽀,代码如下
ServiceA {
/
**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
try {
} catch (SomeException) {
// 执⾏其他业务, 如 hodC();
}
}spring roll怎么读
}
这种⽅式也是潜套事务最有价值的地⽅, 它起到了分⽀执⾏的效果, 如果 hodB 失败, 那么执⾏ hodC(), ⽽ hodB 已经回滚到它执⾏之前的 SavePoint, 所以不会产⽣脏数据(相当于此⽅法从未执⾏过), 这种特性可以⽤在某些特殊的业务中, ⽽ PROPAGATION_REQUIRED 和PROPAGATION_REQUIRES_NEW 都没有办法做到这⼀点。
2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么⾸先 hodB 回滚到它执⾏之前的 SavePoint(在任何情况下都会如此), 外
部事务(即 ServiceA#methodA) 将根据具体的配置决定⾃⼰是 commit 还是 rollback。
三、嵌套事务总结
使⽤嵌套事务的场景有两点需求:
需要事务BC与事务AD⼀起commit,即:作为事务AD的⼦事务,事务BC只有在事务AD成功commit时(阶段3成功)才commit。这个需求简单称之为“联合成功”。这⼀点PROPAGATION_NESTED和PROPAGATION_REQUIRED可以做到。
需要事务BC的rollback不(⽆条件的)影响事务AD的commit。这个需求简单称之为“隔离失败”。这⼀点PROPAGATION_NESTED和
PROPAGATION_REQUIRES_NEW可以做到。
分解下,可知PROPAGATION_NESTED的特殊性有:
1、使⽤PROPAGATION_REQUIRED满⾜需求1,但⼦事务BC的rollback会⽆条件地使⽗事务AD也rollback,不能满⾜需求2。即使对⼦事务进⾏了try-catch,⽗事务AD也不能commit。⽰例见2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
2、使⽤PROPAGATION_REQUIRES_NEW满⾜需求2,但⼦事务(这时不应该称之为⼦事务)BC是完全新的事务上下⽂,⽗事务(这时也不应该称之为⽗事务)AD的成功与否完全不影响BC的提交,不能满⾜需求1。
同时满⾜上述两条需求就要⽤到PROPAGATION_NESTED了。PROPAGATION_NESTED在事务AD执⾏到B点时,设置了savePoint(关键)。
当BC事务成功commit时,PROPAGATION_NESTED的⾏为与PROPAGATION_REQUIRED⼀样。只有当事务AD在D点成功commit时,事务BC才真正commit,如果阶段3执⾏异常,导致事务AD rollback,事务BC也将⼀起rollback ,从⽽满⾜了“联合成功”。
当阶段2执⾏异常,导致BC事务rollback时,因为设置了savePoint,AD事务可以选择与BC⼀起rollback
或继续阶段3的执⾏并保留阶段1的执⾏结果,从⽽满⾜
了“隔离失败”。
当然,要明确⼀点,事务传播策略的定义是在声明或事务管理范围内的(⾸先是在EJB CMT规范中定义,Spring事务框架补充了PROPAGATION_NESTED),编程式的事务管理不存在事务传播的问题。
四、PROPAGATION_NESTED的必要条件
上⾯⼤致讲述了潜套事务的使⽤场景, 下⾯我们来看如何在 spring 中使⽤ PROPAGATION_NESTED, ⾸先来看 AbstractPlatformTransactionManager
/**
* Create a TransactionStatus for an existing transaction.
*/
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
... 省略
if (PropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + Name() + "]");
}
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status =
newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
return status;
}
else {
// Nested transaction through nested begin and commit/rollback calls.
/
/ Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction.
doBegin(transaction, definition);
boolean newSynchronization = (ansactionSynchronization != SYNCHRONIZATION_NEVER);
return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
}
}
}
1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!!
再看 AbstractTransactionStatus#createAndHoldSavepoint() ⽅法
/**
* Create a savepoint and hold it for the transaction.
* @throws ansaction.NestedTransactionNotSupportedException
* if the underlying transaction does not support savepoints
*/
public void createAndHoldSavepoint() throws TransactionException {
setSavepoint(getSavepointManager().createSavepoint());
}
可以看到 Savepoint 是 ateSavepoint 实现的, 再看 SavepointManager 的层次结构, 发现
其 Template 实现是 JdbcTransactionObjectSupport, 常⽤的 DatasourceTransactionManager, HibernateTransactionManager
中的 TransactonObject 都是它的⼦类 :
JdbcTransactionObjectSupport 告诉我们必须要满⾜两个条件才能 createSavepoint :
2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+
3. MetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须⽀持 JDBC 3.0 确保以上条件都满⾜后, 你就可以尝试使⽤ PROPAGATION_NESTED 了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论