一个Hibernate的事务问题

866 阅读2分钟

本文首发于 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应该是一样的啊。

//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/89f0009c2eb04b328e354aa893b6468c~tplv-k3u1fbpfcp-zoom-1.image

Hibernate版本:4.1.8.Final

阅读原文