文章标签 » hibernate

一个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

从 Hibernate 3 升级到 Hibernate 4 序列号(Sequence)不能用 String 了?(已解决)

从 Hibernate 3 升级到 Hibernate 4 ,出现了这样的错误:

1
2
3
4
5
6
org.hibernate.id.IdentifierGenerationException: Unknown integral data type for ids : java.lang.String
at org.hibernate.id.IdentifierGeneratorHelper.getIntegralDataTypeHolder(IdentifierGeneratorHelper.java:215)
at org.hibernate.id.SequenceGenerator.buildHolder(SequenceGenerator.java:150)
at org.hibernate.id.SequenceGenerator.generateHolder(SequenceGenerator.java:126)
at org.hibernate.id.SequenceGenerator.generate(SequenceGenerator.java:116)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:118)

原来,在 Hibernate 4 中如果主键的生成策略是 Sequence ,则要求数据库中对应的字段必须为数字(Number,Long 等)类型。
而当前系统中使用的是字符串类型,所以报错了。

网上参考了几篇文章,就一篇(见文末)还不错。在此基础上,覆写了部分方法,解决了问题。

首先是 HISequenceGenerator 类

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package org.young.hibernate.common;

import java.io.Serializable;

import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.id.SequenceGenerator;

/**
 * 继承 hibernate 里的 SequenceGenerator 类,
 * 覆写2个方法:
 * 	1. buildHolder()
 * 	2. generate(SessionImplementor session, Object obj)
 *
 * @author by youngz
 *      on 2016年7月24日
 *
 * Package&FileName: org.young.hibernate.common.HISequenceGenerator
 */
public class HISequenceGenerator extends SequenceGenerator {

	@Override
	public Serializable generate(SessionImplementor session, Object obj) {
		
		/*
		 * 源码
		 * 
		 * return generateHolder(session).makeValue();
		 */
//		Serializable id = generateHolder(session).makeValue();
		Number id = generateHolder(session).makeValue();
		
		/*
		 * 笔者曾自作聪明地以为可以省掉这一步,
		 * 继而这个方法就不用覆写,毕竟都是 Serializable ,
		 * 不管是数字(Number)还是字符串(String)
		 * 
		 * 但返回值却需要确定到String,因为Java类(hbm对象)的属性是String类型的
		 * 而 makeValue() 返回的 Number (这里的实际类型是 Long)
		 * 
		 * 所以 id 的类型直接定义成 Number (更直观),
		 * 而不是 Serializable
		 */
        if (getIdentifierType().getReturnedClass() == String.class){
        	//增加对String的判断
            return id.toString();
        }
        
        return id;
	}

	@Override
	protected IntegralDataTypeHolder buildHolder() {
		/*
		 * 源码
		 * 
		 * return IdentifierGeneratorHelper.getIntegralDataTypeHolder(this.identifierType.getReturnedClass());
		 */
		return HIIdentifierGeneratorHelper
				.getIntegralDataTypeHolder(getIdentifierType()
						.getReturnedClass());
	}
}

还有一个辅助类 HIIdentifierGeneratorHelper

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package org.young.hibernate.common;

import java.math.BigDecimal;
import java.math.BigInteger;

import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.id.IdentifierGeneratorHelper;
import org.hibernate.id.IntegralDataTypeHolder;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * IdentifierGeneratorHelper ,final 类,不可继承
 *
 * @author by youngz
 *      on 2016年7月24日
 *
 * Package&FileName: org.young.hibernate.common.HIIdentifierGeneratorHelper
 */
public class HIIdentifierGeneratorHelper {

	public static IntegralDataTypeHolder getIntegralDataTypeHolder(
			Class integralType) {
		if ((integralType == Long.class) || (integralType == Integer.class)
				|| (integralType == Short.class)
				// 增加对String的判断
				|| (integralType == String.class)) {
			return new HIBasicHolder(integralType);
		}
		if (integralType == BigInteger.class)
			return new IdentifierGeneratorHelper.BigIntegerHolder();

		if (integralType == BigDecimal.class) {
			return new IdentifierGeneratorHelper.BigDecimalHolder();
		}

		throw new IdentifierGenerationException(
				"Unknown integral data type for ids : "
						+ integralType.getName());
	}

	public static class HIBasicHolder implements IntegralDataTypeHolder {
		private final Class exactType;
		private long value = 0L;

		public HIBasicHolder(Class exactType) {
			
			this.exactType = exactType;
			if ((exactType != Long.class) && (exactType != Integer.class)
					&& (exactType != Short.class)
					// 增加对String的判断
					&& (exactType != String.class)
					)
				throw new IdentifierGenerationException(
						"Invalid type for basic integral holder : " + exactType);
		}
		
		public long getActualLongValue() {
			return this.value;
		}

		public IntegralDataTypeHolder initialize(long value) {
			this.value = value;
			return this;
		}

		public IntegralDataTypeHolder initialize(ResultSet resultSet,
				long defaultValue) throws SQLException {
			long value = resultSet.getLong(1);
			if (resultSet.wasNull())
				value = defaultValue;

			return initialize(value);
		}

		public void bind(PreparedStatement preparedStatement, int position)
				throws SQLException {
			preparedStatement.setLong(position, this.value);
		}

		public IntegralDataTypeHolder increment() {
			checkInitialized();
			this.value += 1L;
			return this;
		}

		private void checkInitialized() {
			if (this.value == 0L)
				throw new IdentifierGenerationException(
						"integral holder was not initialized");
		}

		public IntegralDataTypeHolder add(long addend) {
			checkInitialized();
			this.value += addend;
			return this;
		}

		public IntegralDataTypeHolder decrement() {
			checkInitialized();
			this.value -= 1L;
			return this;
		}

		public IntegralDataTypeHolder subtract(long subtrahend) {
			checkInitialized();
			this.value -= subtrahend;
			return this;
		}

		public IntegralDataTypeHolder multiplyBy(IntegralDataTypeHolder factor) {
			return multiplyBy(IdentifierGeneratorHelper.extractLong(factor));
		}

		public IntegralDataTypeHolder multiplyBy(long factor) {
			checkInitialized();
			this.value *= factor;
			return this;
		}

		public boolean eq(IntegralDataTypeHolder other) {
			return eq(IdentifierGeneratorHelper.extractLong(other));
		}

		public boolean eq(long value) {
			checkInitialized();
			return (this.value == value);
		}

		public boolean lt(IntegralDataTypeHolder other) {
			return lt(IdentifierGeneratorHelper.extractLong(other));
		}

		public boolean lt(long value) {
			checkInitialized();
			return (this.value < value);
		}

		public boolean gt(IntegralDataTypeHolder other) {
			return gt(IdentifierGeneratorHelper.extractLong(other));
		}

		public boolean gt(long value) {
			checkInitialized();
			return (this.value > value);
		}

		public IntegralDataTypeHolder copy() {
			HIBasicHolder copy = new HIBasicHolder(this.exactType);
			copy.value = this.value;
			return copy;
		}

		public Number makeValue() {
			checkInitialized();
			if (this.exactType == Long.class)
				return Long.valueOf(this.value);
			
			// 增加对String的判断
			if (this.exactType == String.class) {
				return Long.valueOf(this.value);
			}

			if (this.exactType == Integer.class) {
				return Integer.valueOf((int) this.value);
			}

			return Short.valueOf((short) (int) this.value);
		}

		public Number makeValueThenIncrement() {
			Number result = makeValue();
			this.value += 1L;
			return result;
		}

		public Number makeValueThenAdd(long addend) {
			Number result = makeValue();
			this.value += addend;
			return result;
		}

		public String toString() {
			return "BasicHolder[" + this.exactType.getName() + "[" + this.value
					+ "]]";
		}

		public boolean equals(Object o) {
			if (this == o)
				return true;

			if ((o == null) || (super.getClass() != o.getClass())) {
				return false;
			}

			HIBasicHolder that = (HIBasicHolder) o;

			return (this.value == that.value);
		}

		public int hashCode() {
			return (int) (this.value ^ this.value >>> 32);
		}
	}
}

最后,修改 hibernate 配置文件里的主键生成策略

1
2
3
4
5
6
7
<id name="id" type="java.lang.String">
    <column name="ID" length="20" />
	<!-- 此处的 class 即刚刚新建的 Generator -->
    <generator class="org.young.hibernate.common.HISequenceGenerator" >
    	<param name="sequence">SEQ_ID</param>
    </generator>
</id>

本文的实验是在 hibernate-core-4.1.8.Final.jar 基础上完成。

可至 GitHub 上查看相关代码。

参考:

http://my.oschina.net/liyuj/blog/386582?fromerr=4h5OjIKZ