前段時間寫了本書
《Hibernate 3和Java Persistence API 程序開發(fā)從入門到精通》,書中著重介紹了在Hibernate/JPA中使用Annotation。最近有讀者來信詢問UserType,再加上最近看到有的人在項目中濫用Hibernate的user type,想在這里說幾句。
使用UserType首先要弄清楚它的目的。大家知道Hibernate解決的主要是對象數(shù)據(jù)庫阻抗失衡的問題,也就是如何將一個或多個對象保存到一個或多個數(shù)據(jù)庫表格中。這其中有很多方法,其實大部分情況下采用
@Embeddable和@Embedded就可以解決問題了,只有嵌入對象方式無法滿足要求時,或者是Hibernate默認(rèn)的持久化方式無法滿足要求時,才應(yīng)該考慮UserType??傊涀∫粋€原則,不到山窮水盡,不要輕易使用UserType。還有一個要慎重考慮使用UserType的原因是:一旦采用了UserType,你的項目就脫離了JPA,而直接和Hibernate耦合在一起了。
擴展UserType主要分為兩種:
- immutable
- mutable
今天我先舉個immutable的例子。
Java 5提出了一個新的enum類,JPA提供的標(biāo)準(zhǔn)方法是保存enum的name或者是ordinal。這種默認(rèn)方式能夠滿足新開發(fā)的項目,但是對于一些老項目翻新并不一定適用。下面我們來看一個例子:
public class Status {
public static final int ACTIVATED = 5;
public static final int DEACTIVATED = 6;
}
這個是在java5之前常用的常量定義方法,老項目數(shù)據(jù)庫里面已經(jīng)保存了很多的5啊6的。現(xiàn)在要把Status改寫成enum,而且不希望修改數(shù)據(jù)庫中已有的數(shù)據(jù),怎么做?第一反應(yīng),status enum可以這么寫:
public enum Status {
ACTIVATED,
DEACTIVATED;
}
持久化enum的name屬性是肯定不用考慮了,ordinal屬性呢?這里要保存的可是5和6,而Status enum只有兩個實體,他們的ordinal只是0和1。而且項目中還會有其他很多類似的常量類需要改寫成enum,JPA的默認(rèn)方式無法完成任務(wù),這時候可以開始考慮使用UserType了。
先定義一個接口,這樣可以使用一個UserType支持所有類似的enum:
public interface DescriptionID {
String getDescription();
int getId();
}
然后改寫Status enum:
public enum Status implements DescriptionID {
ACTIVATED(5, "This object is activated"),
DEACTIVATED(9, "This object is deactivated");
private Integer id;
private String description;
private static List<Status> list;
static {
list = new ArrayList<Status>(2);
list.add(ACTIVATED);
list.add(DEACTIVATED);
}
private Status(int statusNr, String description) {
this.id = statusNr;
this.description = description;
}
public String getDescription() {
return this.description;
}
public Integer getId() {
return id;
}
public static List<Status> getAll() {
return list;
}
public static Status findById(Integer id) {
for (Status status : getAll()) {
if (id == status.getId()) {
return status;
}
}
return null;
}
}
注意這里每個enum都必須有兩個static方法,這些方法名必須在所有的enum中保持一致。List()方法是為了方便獲取所有的Status常量,例如在用戶界面通過ComboBox展示,findById()方法是為了通過給定Id獲得對應(yīng)的Enum實例。其中findById()方法參數(shù)一定要是Integer,原因后面會講到。
下面編寫
DescriptionIDUserType:
public class DescriptionIDUserType implements UserType, ParameterizedType {
private Class enumClass;
public void setParameterValues(Properties parameters) {
try {
enumClass = ReflectHelper.classForName(parameters.getProperty("class"));
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Object assemble(Serializable cached, Object arg1)
throws HibernateException {
return cached;
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object)
*/
public Object deepCopy(Object value) throws HibernateException {
// TODO Auto-generated method stub
return value;
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#disassemble(java.lang.Object)
*/
public Serializable disassemble(Object value) throws HibernateException {
// TODO Auto-generated method stub
return (Serializable) value;
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#equals(java.lang.Object,
* java.lang.Object)
*/
public boolean equals(Object id1, Object id2) throws HibernateException {
if (id1 == id2) {
return true;
}
if (id1 == null || id2 == null) {
return false;
}
final DescriptionID did1 = (DescriptionID) id1;
final DescriptionID did2 = (DescriptionID) id2;
return did1.getId() == did2.getId()
&& StringUtils.equals(did1.getDescription(), did2
.getDescription());
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#hashCode(java.lang.Object)
*/
public int hashCode(Object value) throws HibernateException {
// TODO Auto-generated method stub
return value.hashCode();
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#isMutable()
*/
public boolean isMutable() {
// TODO Auto-generated method stub
return false;
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet,
* java.lang.String[], java.lang.Object)
*/
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException {
try {
int id = resultSet.getInt(names[0]);
if (resultSet.wasNull()) {
return null;
}
return enumClass.getMethod("findById", new Class[] { Integer.class })
.invoke(null, id);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement,
* java.lang.Object, int)
*/
public void nullSafeSet(PreparedStatement statement, Object value, int index)
throws HibernateException, SQLException {
if (value == null) {
statement.setNull(index, Hibernate.INTEGER.sqlType());
} else {
DescriptionID dID = (DescriptionID) value;
statement.setInt(index, dID.getId());
}
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#replace(java.lang.Object,
* java.lang.Object, java.lang.Object)
*/
public Object replace(Object original, Object arg1, Object arg2)
throws HibernateException {
// TODO Auto-generated method stub
return original;
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#returnedClass()
*/
public Class returnedClass() {
return DescriptionID.class;
}
/*
* (non-Javadoc)
*
* @see org.hibernate.usertype.UserType#sqlTypes()
*/
public int[] sqlTypes() {
return new int[]{Hibernate.INTEGER.sqlType()};
}
}
我們的這個UserType是要支持實現(xiàn)
DescriptionID的各種不同的enum,而enum是沒法繼承的。所以我們需要用戶給出具體的參數(shù),以進一步確定到底是哪個enum類。這也就導(dǎo)致了,我們的這個類需要實現(xiàn)ParameterizedType接口。
由于enum類本身是immutable的,所以這個UserType的實現(xiàn)類相對比較簡單,主要的兩個方法是nullSafeGet和nullSafeSet。在nullSaftGet中我們使用Java Reflection并借助用戶給出的enum類參數(shù)直接調(diào)用該enum類的findById()方法,這樣我們就可以使用數(shù)據(jù)庫中的integer找到對應(yīng)的enum實例。注意,由于使用了Java Reflection,所以findById()方法參數(shù)必須是Integer而非int。 在nullSafeSet中,我們則通過DescriptionID接口直接獲取enum實例的id屬性,并且將它保存到數(shù)據(jù)庫中去。
最后看看怎么使用這個UserType:
@TypeDefs({@TypeDef(name = "status", typeClass = DescriptionIDUserType.class,
parameters = {@Parameter(name = "class", value = "com.yourpackage.Status")})})
@Entity
public class SomeObject {
private Integer objectId;
private Status status;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Integer getObjectId() {
return objectId;
}
public void setObjectId(Integer objectId) {
this.objectId = objectId;
}
@Type(type = "status")
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
其中值得講講的就是定義Type時使用的parameter,"class"參數(shù)是我們自己定義的,該參數(shù)為
DescriptionIDUserType提供了具體的enum類。前面已經(jīng)講過了,
DescriptionIDUserType就是在運行時態(tài)利用這個參數(shù)自定義enum與數(shù)據(jù)庫之間的持久化邏輯。
使用這個UserType之后,我們就可以在確保數(shù)據(jù)庫數(shù)據(jù)不變的情況下,成功地將類型不保險的常量類改寫成enum,而且這個UserType支持所有實現(xiàn)了DescriptionID接口的enum類。
類似的情況朋友們可以自由發(fā)揮了。
關(guān)于Annotation和Usertype的相關(guān)知識請參考我寫的《Hibernate 3和Java Persistence API 程序開發(fā)從入門到精通》
聲明:本文版權(quán)歸作者所有,如需轉(zhuǎn)載請注明出處。