目前,似乎很少有支持嵌套事务的中间件,但嵌套事务确实存在。
假定有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();