目前,似乎很少有支持嵌套事务的中间件,但嵌套事务确实存在。
假定有Method A, Method B, Method C
A 调用 B,C
ServiceHost {
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void invoke() {
try{
ServiceA.invoke()
} catch (Bussiness.A.Exception) {
abort()
}
try {
ServiceB.invoke();
} catch (Bussiness.B.Exception) {
ServiceC.invoke();
} catch (Bussiness.C.Exception) {
ServiceD.invoke();
} catch (Bussiness.D.Exception) {
abort()
}
try{
ServiceE.invoke()
} catch (Bussiness.E.Exception) {
ServiceF.invoke()
}catch (Bussiness.F.Exception) {
abort()
}
}
}
ServiceA {
/**
* 事务属性配置为 PROPAGATION_NESTED,
× 即该事务需要被嵌套
*/
void methodA() {
}
}
ServiceA, ServiceB, ServiceC, ServiceD, ServiceE, ServiceF都配置为PROPAGATION_NESTED
通过这样的嵌套规约,我们可以满足业务完整性的需求,一个ServiceParent 业务,只允许出现A-B-E , A-B-F, A-C-E, A-C-F, A-D-E,A-D-F的组合,其他组合,如A-B-C, A-E, B-F都是不允许的。
术语上,ServiceA, B, C, D, E, F的事务均是ServiceParent事务的子事务,现在,是ServiceParent嵌套ServiceA-F,且嵌套事务的几个特性如下:
1, 假定子事务(ServiceA-F)处于活动状态(active),则parent事务(ServiceParent)不能执行任何其他操作,父事务只能commit事务,abort事务以及创建更多其它子事务。
2, 若子事务(ServiceA)被提交了,此时父事务则能够看到子事务做出的任何修改,这些修改,对其他子事务来说是隐藏的,直到父事务提交为止。
3, 同样地,如果被嵌套的事务ServiceA被回滚了,则它也不会对父事务有任何影响,且父事务servieParent也不会看到ServiceA曾经修改过的数据。
4, 最终父事务提交、回滚才会决定到子事务的提交、回滚。表达的形象点,若ServiceA“提交”后,ServiceParent却因为Service B/C/D都无法成功执行,ServiceA会回滚。这种做法依赖于JDBC 3.0 SavePoint技术。
5, 嵌套子事务提交后,它对DB的锁其实并没有释放,这些锁的控制权被转交给父事务,直到父事务提交(或回滚)后,锁才会真正释放
6, 目前,嵌套的深度不收控制,也就是,我们可对method进行以深度嵌套。
现在,我们略略了解一下JDBC SavePoint技术,它给予了我们这样的能力,在一个事务里面,我们能够将已经提交的事务状态,恢复到一个事务commit以前的任意定点(这个点就是SavePoint)。
举个例子,假设我们面临这样一种情况,methodA是运算代价非常大的事务操作,methodB, methodC都是轻量级的事务,我们需要执行这样一个事务Tx={methodA->methodB->methodC}。
没有SavePoint之前,如果methodA执行成功,但是恰恰methodB失败了,那么methodA所有成果必须回滚,methodC也别想执行了,因为methodA、methodB、methodC都在同一个Tx事务中;JDBC3.0的SavePoint技术为我们提供了一个无需回滚MethodA的折衷办法,可以让我们在保留methodA的成果的同时,仅仅回滚methodB,然后继续执行methodC。
回滚到SavePoint的代码类似于:
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate("INSERT INTO TAB1 (COL1) VALUES " +
"(’FIRST’)");
// set savepoint
Savepoint svpt1 = conn.setSavepoint("SAVEPOINT_1");
rows = stmt.executeUpdate("INSERT INTO TAB1 (COL1) " +
"VALUES (’SECOND’)");
conn.rollback(svpt1);
conn.commit();