本文首发于 www.YoungZY.com/
工作中遇到一个问题。
为了方便说明,做了简化。
业务要求:有两个接口,要么都成功,要么都失败。即任一个接口调用失败,两个接口相关的数据(如果库里有的话)都要删除。
假设:tab_two 中已有一条数据,再调接口时会报主键冲突。
期望的结果:第一次调用时失败,并把tab_two 中已有的一条数据清除。第二次调用成功。
public class SomeInterface {
public void execute() {
try {
if (m1) {
method1();
} else if (m2) {
method2();
}
} catch (Exception e) {
hasException = true;
} finally {
if (hasException) {
clearData();
}
}
}
private void method1() {
// 用Hibernate写表 tab_one
}
private void method2() {
// 用Hibernate写表 tab_two
}
private void clearData() {
// 使用JDBC
// 删除表 tab_one
// 删除表 tab_two
}
}
问题1:某一接口失败时,其数据未被清除。
伪码如上。写表Hibernate,删表JDBC
接口2失败后,tab_one 的数据被清除了,但tab_two的数据还在。
猜测:Hibernate和JDBC有不同的事务处理机制。
以前也有类似的问题:
{
...
saveByHibernate();
// 调存储过程时会报错:用Hibernate存的表没有数据
invokeProduce();
...
}
// 修改后
{
...
saveByHibernate();
// 把Hibernate存的数据再查一下,执行存储过程就没问题了
load();
invokeProduce();
...
}
基于以上经验,修改代码:写表删表都用Hibernate。
经过测试,还是不行,失败接口的数据仍然存在。Debug的过程中发现,确实有delete,但删的是表里已有的数据,新数据(当前调用接口的数据)并未被删除,事务结束时被写入了表中。
想到Hibernate对象的三种状态:瞬时态(Transient)、持久态(Persistent)、脱管/游离态(Detached)于是使用了 evict(obj) 方法,结果还是不行。
public void clearData(Object errorData) {
getSession().evict(errorData);
// 使用Hibernate
// 删除表1对象,删之前先查
obj1 = load();
delete(obj1);
// 删除表2对象
obj2 = load();
delete(obj2);
}
通过日志发现,load之前会有insert,好像有个隐式的flush。
最后决定使用 getSession().clear() 试一下,居然意外地解决了。
没太搞清楚其中的原理,按照下图所示的各种状态之间的流转,evict和clear应该是一样的啊。
Hibernate版本:4.1.8.Final