最近看到hibernate的自定义类型,这个以前没接触过,在这里记录一下,当是对自己知识的巩固,也让没有接触过的朋友一起学习研究一番。
1)自定义类型,顾名思义,当然就是由于内部的类型不满足需求,而自己来进行实现的类型。这种情况不多,但我们还是有必要学习一下,技多不压身嘛。也学习一下,别人在做框架的时候是怎么去考虑的,怎么去思考扩展性的。
自定义类型有两个方法来实现,一种是实现UserType,另外一种实现CompositeUserType,另外可能还有一些方法,但我暂时没用到,先不讲了。
我暂时只用到UserType,我们就先看一下UserType接口的定义:
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
|
public interface UserType {
/**
* Return the SQL type codes for the columns mapped by this type. The
* codes are defined on <tt>java.sql.Types</tt>.
*/
public int [] sqlTypes();
/**
* The class returned by <tt>nullSafeGet()</tt>.
*/
public Class returnedClass();
/**
* Compare two instances of the class mapped by this type for persistence "equality".
* Equality of the persistent state.
*/
public boolean equals(Object x, Object y) throws HibernateException;
/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
public int hashCode(Object x) throws HibernateException;
/**
* Retrieve an instance of the mapped class from a JDBC resultset. Implementors
* should handle possibility of null values.
*/
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException;
/**
* Write an instance of the mapped class to a prepared statement. Implementors
* should handle possibility of null values. A multi-column type should be written
* to parameters starting from <tt>index</tt>.
*/
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException;
/**
* Return a deep copy of the persistent state, stopping at entities and at
* collections. It is not necessary to copy immutable objects, or null
* values, in which case it is safe to simply return the argument.
*/
public Object deepCopy(Object value) throws HibernateException;
/**
* Are objects of this type mutable?
*
* @return boolean
*/
public boolean isMutable();
/**
* Transform the object into its cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. That may not be enough
* for some implementations, however; for example, associations must be cached as
* identifier values. (optional operation)
*
* @param value the object to be cached
* @return a cachable representation of the object
* @throws HibernateException
*/
public Serializable disassemble(Object value) throws HibernateException;
/**
* Reconstruct an object from the cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. (optional operation)
*/
public Object assemble(Serializable cached, Object owner) throws HibernateException;
/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For
* mutable objects, it is safe to return a copy of the first parameter. For objects
* with component values, it might make sense to recursively replace component values.
*/
public Object replace(Object original, Object target, Object owner) throws HibernateException;
}
|
其实大家看英文一般情况下都能理解,不再多做解释了,这里我们最主要的就是实现nullSafeSet() 方法,这个方法主要用到把此类型的值保存到数据库,这一次我们先学怎么用,以后我们再慢慢研究内部是怎么来实现的。
2)我学习时写的例子是参照夏昕的例子,所以肯定和网上的大部分都一样,我们只是大概分析一下:
下面是User类
1
2
3
4
5
6
7
8
9
|
package org.hibernate.tutorial.domain;
import java.io.Serializable;
import java.util.List;
public class User implements Serializable{
public Long id;
private String name;
private List emails;
省略Get/Set方法
}
|
下来是自定义的EmailList类:
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
|
package org.hibernate.tutorial.domain;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
public class EmailList implements UserType {
private static final char SPLITTER = ';' ;
private static final int [] TYPES = new int [] {Types.VARCHAR};
private String assemble(List emailList) {
StringBuilder strBuf = new StringBuilder();
for ( int i = 0 ; i < emailList.size() - 1 ; i++){
strBuf.append(emailList.get(i)).append(SPLITTER);
}
strBuf.append(emailList.get(emailList.size()- 1 ));
return strBuf.toString();
}
private List parse(String value) {
String[] strs = org.hibernate.util.StringHelper.split(value,String.valueOf(SPLITTER));
List emailList = new ArrayList();
for ( int i = 0 ;i < strs.length; i++) {
emailList.add(strs[i]);
}
return emailList;
}
public Object deepCopy(Object value) throws HibernateException {
List sourceList = (List)value;
List targetList = new ArrayList();
targetList.add(sourceList);
return targetList;
}
public Serializable disassemble(Object value) throws HibernateException {
return null ;
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) return true ;
System.out.println( "X:" +x+ "Y:" +y);
if (x != null && y != null ) {
List xList = (List)x;
List yList = (List)y;
if (xList.size() != yList.size()) return false ;
for ( int i = 0 ; i < xList.size(); i++) {
String str1 = (String)xList.get(i);
String str2 = (String)yList.get(i);
if (!str1.equals(str2)) return false ;
}
return true ;
}
return false ;
}
public boolean isMutable() {
return false ;
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
String value = (String)Hibernate.STRING.nullSafeGet(rs, names[ 0 ]);
if (value != null ) {
return parse(value); //把List通过;分割
} else {
return null ;
}
}
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
System.out.println( "Set Method Executed!" );
System.out.println( "value:" + value);
if (value != null ){
String str = assemble((List)value); //把字符串用;拼接
Hibernate.STRING.nullSafeSet(st, str, index);
} else {
Hibernate.STRING.nullSafeSet(st, value, index);
}
}
public Class returnedClass() {
return List. class ;
}
public int [] sqlTypes() {
return TYPES;
}
//省略其他不需要修改的方法
}
|
类中实现的方法是需要修改的方法,其他不需要修改暂时不用的方法则没有写出来,但还是需要实现的。
3)接下来就是User类的映射文件:
1
2
3
4
5
6
7
|
< class name = "User" table = "USER" >
< id name = "id" column = "USER_ID" type = "java.lang.Long" >
< generator class = "native" />
</ id >
< property name = "name" type = "string" column = "USER_NAME" />
< property name = "emails" type = "org.hibernate.tutorial.domain.EmailList" column = "emails" />
</ class >
|
相信大家都知道怎么进行修改,这里也不进行讲解了,主要是修改emails的type,修改为我们刚才定义的EmailList类。
4)最后我们来写一个测试类:
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
|
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import junit.framework.TestCase;
import org.hibernate.EntityMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.tutorial.domain.User;
public class HibernateTest extends TestCase{
private Session session = null ;
protected void setUp() throws Exception {
Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory = cfg.buildSessionFactory();
session = sessionFactory.openSession();
}
public void testInsert(){
Transaction tran = null ;
try {
tran = session.beginTransaction();
User user = new User();
user.setName( "shun" );
List list = new ArrayList();
list.add( "12312@sfsdf.com" );
list.add( "123@123.com" );
user.setEmails(list);
session.save(user);
tran.commit();
} catch (Exception ex) {
ex.printStackTrace();
if (tran != null ){
tran.rollback();
}
}
}
protected void tearDown() throws Exception {
session.close();
}
}
|
这里可能会出现问题,当我们只保存一个email时,它会出现异常,在数据库里面是email字段是空的,而当我们如上面代码一样,有两个时,并不会出现问题,数据库中结果如图:
而当我们只保存一个时,异常如下:
1
|
java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.String
|
它发生在EmailList的equals方法中的String str1 = (String)xList.get(i);这句代码中,经检查是在插入数据传到EmailList的nullSafeSet方法时变成了List的List,即
value:[[12312@sfsdf.com, 123@123.com]]这样的形式,这样在比较的时候就会出问题,它永远都只有一个值,而在比较的时候却是不同的,
1
|
if (xList.size() != yList.size()) return false ;
|
所以在强制转换时会出问题。
而经过检查,equals方法里:
1
|
X:[[ 12312 @sfsdf .com, 123 @123 .com]]Y:[ 12312 @sfsdf .com, 123 @123 .com]
|
这样的结果却是很奇怪的。网上并没有讲到为什么会出现这种情况。这里提出一下:我用的hibernate版本是Hibernate 3.3.2.GA。不知道是版本问题还是其他问题,我们明天再研究一下。如果有哪位兄弟知道为什么的,希望也不吝告诉我一下。