一个Hibernate的事务问题

工作中遇到一个问题。为了方便说明,做了简化。

业务要求:有两个接口,要么都成功,要么都失败。即任一个接口调用失败,两个接口相关的数据(如果库里有的话)都要删除。

假设:tab_two 中已有一条数据,再调接口时会报主键冲突。

期望的结果:第一次调用时失败,并把tab_two 中已有的一条数据清除。第二次调用成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
    }
}

问题:某一接口失败时,其数据未被清除。

伪码如上。写表Hibernate,删表JDBC。

接口2失败后,tab_one 的数据被清除了,但tab_two的数据还在。

猜测:Hibernate和JDBC有不同的事务处理机制。

以前也有类似的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    ...
    saveByHibernate();
    // 调存储过程时会报错:用Hibernate存的表没有数据
    invokeProduce();
    ...
}

// 修改后
{
    ...
    saveByHibernate();
    // 把Hibernate存的数据再查一下,执行存储过程就没问题了
    load();
    invokeProduce();
    ...
}

基于以上经验,修改代码:写表删表都用Hibernate。

经过测试,还是不行,失败接口的数据仍然存在。Debug的过程中发现,确实有delete,但删的是表里已有的数据,新数据(当前调用接口的数据)并未被删除,事务结束时被写入了表中。

想到Hibernate对象的三种状态:瞬时态(Transient)、持久态(Persistent)、脱管/游离态(Detached),于是使用了 evict(obj) 方法,结果还是不行。

1
2
3
4
5
6
7
8
9
10
11
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-status

Hibernate版本:4.1.8.Final

加入讨论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据