?? ?Hibernate提供客戶化映射類型接口,使用戶能以編程方式創建自定義的映射類型來將持久化類任意類型的屬性映射到數據庫中。使用客戶化映射類型,需要實現org.hibernate.usertype.UserType接口。這是個強大的功能,也是Hibernate的最佳實踐之一。我們經常提到ORM中很困難的一點便是O的屬性和R的屬性不能一一映射,而Hibernate提供的UserType無疑給出了一個很好的解決方案。本文給出使用客戶化映射類型的兩個例子,算是對Hibernate初學者的拋磚。
?? ?第一個例子是使用UserType映射枚舉類型。假設Account表中含有一sex列,類型為tinyint(當前其0代表男,1代表女,將來可能出現2等代表其他性別類型);我們當然可以在對應的Account類中添加int類型的sex屬性,但這種數字化無顯示意義且類型不安全的枚舉不是很好的解決方式,這里就采用了java5的enum來作為Account類的性別屬性(如果不熟悉java5的enum,也可采用《effective java》中提到的經典的類型安全的枚舉方案)。在Account添加enum Gender:
public?class?Account?extends?AbstractDomain<Long>{
????
????public?enum?Gender{
????????Male("male",0),
????????Female("female",1);
????????
????????private?String?name;
????????private?int?value;
????????
????????public?String?getName()?{
????????????return?name;
????????}
????????public?int?getValue()?{
????????????return?value;
????????}
????????
????????private?Gender(String?name,int?value){
????????????this.name?=?name;
????????????this.value?=?value;
????????}
????????
????????public?static?Gender?getGender(int?value){
????????????if(0?==?value)return?Male;
????????????else?if(1?==?value)return?Female;
????????????else?throw?new?RuntimeException();
????????}
????????
????}
????
????private?Gender?gender;
????public?Gender?getGender()?{
????????return?gender;
????}
????public?void?setGender(Gender?gender)?{
????????this.gender?=?gender;
????}
???????//省略其他????
}
?? ?接下來定義實現UserType接口的GenderUserType:
public?class?GenderUserType?implements?UserType{
????public?Object?assemble(Serializable?arg0,?Object?arg1)?throws?HibernateException?{
????????return?null;
????}
????/*
?????*??這是用于Hibernate緩存生成的快照,由于Gender是不可變的,直接返回就好了。
?????*/
????public?Object?deepCopy(Object?arg0)?throws?HibernateException?{
????????return?arg0;
????}
????public?Serializable?disassemble(Object?arg0)?throws?HibernateException?{
????????return?null;
????}
????/*
?????*?由于Gender是不可變的,因此直接==了,這個方法將在insert、update時用到。
?????*/
????public?boolean?equals(Object?x,?Object?y)?throws?HibernateException?{
????????return?x?==?y;
????}
????public?int?hashCode(Object?o)?throws?HibernateException?{
????????return?o.hashCode();
????}
????/*
?????*?表明Gender是不是可變類(很重要的概念哦),這里的Gender由于是枚舉所以是不可變的
?????*/
????public?boolean?isMutable()?{
????????return?false;
????}
????/*
?????*??從ResultSet讀取sex并返回Gender實例,這個方法是在從數據庫查詢數據時用到。
?????*/
????public?Object?nullSafeGet(ResultSet?rs,?String[]?names,?Object?owner)?throws?HibernateException,?SQLException?{
????????int?value?=?rs.getInt(names[0]);
????????return?Account.Gender.getGender(value);
????}
????/*
?????*??將Gender的value設置到PreparedStatement。
?????*/
????public?void?nullSafeSet(PreparedStatement?ps,?Object?value,?int?index)?throws?HibernateException,?SQLException?{
??????? if(value == null){
??? ??? ??? ps.setInt(index,Account.Gender.Male.getValue());
??? ??? }else{
??? ??? ??? ps.setInt(index,((Account.Gender)value).getValue());
??? ??? }
????}
????public?Object?replace(Object?arg0,?Object?arg1,?Object?arg2)?throws?HibernateException?{
????????return?null;
????}
????/*
?????*?設置映射的Gender類
?????*/
????public?Class?returnedClass()?{
????????return?Account.Gender.class;
????}
????/*
?????*??設置Gender枚舉中的value屬性對應的Account表中的sex列的SQL類型
?????*/
????public?int[]?sqlTypes()?{
????????int[]?typeList?=?{Types.TINYINT};
????????return?typeList;
????}
}
?? ?最后在Account的配置文件中配置gender屬性就好了:
<property name="gender" type="org.prague.domain.util.GenderUserType" column="sex"></property>
?? ?除了可以使用 UserType映射枚舉類型,也可以使用Hibernate的PersistentEnum來實現同樣的功能,感興趣的朋友可以參考文章http://www.hibernate.org/203.html。
?? ?
?? ?第二個例子是關于email的。假設Account表中email是一個varchar型的字段,而Account中的Email是如下的類:
public?class?Email?{
????String?username;
????String?domain;
????public?Email()?{
????}
????public?Email(String?username,?String?domain)?{
????????this.username?=?username;
????????this.domain?=?domain;
????}
????public?String?getUsername()?{
????????return?username;
????}
????public?String?getDomain()?{
????????return?domain;
????}
????
????public?void?setDomain(String?domain)?{
????????this.domain?=?domain;
????}
????public?void?setUsername(String?username)?{
????????this.username?=?username;
????}
????public?String?toString()?{
????????return?username?+?'@'?+?domain;
????}
????public?static?Email?parse(String?email)?{
????????Email?e?=?new?Email();
????????int?at?=?email.indexOf('@');
????????if?(at?==?-1)?{
????????????throw?new?IllegalArgumentException("Invalid?email?address");
????????}
????????e.username?=?email.substring(0,?at);
????????e.domain?=?email.substring(at?+?1);
????????return?e;
????}
????@Override
????public?int?hashCode()?{
????????final?int?PRIME?=?31;
????????int?result?=?1;
????????result?=?PRIME?*?result?+?((domain?==?null)???0?:?domain.hashCode());
????????result?=?PRIME?*?result?+?((username?==?null)???0?:?username.hashCode());
????????return?result;
????}
????@Override
????public?boolean?equals(Object?obj)?{
????????if?(this?==?obj)????return?true;
??????if(null?==?obj)return?false;
????????if?(getClass()?!=?obj.getClass())
????????????return?false;
????????final?Email?other?=?(Email)?obj;
????????if?(domain?==?null)?{
????????????if?(other.domain?!=?null)
????????????????return?false;
????????}?else?if?(!domain.equals(other.domain))
????????????return?false;
????????if?(username?==?null)?{
????????????if?(other.username?!=?null)
????????????????return?false;
????????}?else?if?(!username.equals(other.username))
????????????return?false;
????????return?true;
????}
}
????email是Account類的一個屬性:
public?class?Account?extends?AbstractDomain<Long>{
????
????private?Email?email;
????public?Email?getEmail()?{
????????return?email;
????}
????public?void?setEmail(Email?email)?{
????????this.email?=?email;
????}
????//省略其他????
}
?? ?這樣的情況下,需要將email的username + '@' + domain映射到Account表的email列,定義一個EmailUserType如下:
?
???public?class?EmailUserType?implements?UserType{
????public?Object?assemble(Serializable?arg0,?Object?arg1)?throws?HibernateException?{
????????return?null;
????}
????public?Object?deepCopy(Object?o)?throws?HibernateException?{
????????if(null?==?o)return?null;
????????Email?e?=?(Email)o;
????????return?new?Email(e.getUsername(),e.getDomain());
????}
????public?Serializable?disassemble(Object?arg0)?throws?HibernateException?{
????????return?null;
????}
????public?boolean?equals(Object?x,?Object?y)?throws?HibernateException?{
????????if(x?==?y)return?true;
????????if(x?==?null?||?y?==?null)return?false;
????????boolean??f?=?x.equals(y);
????????return?f;
????}
????public?int?hashCode(Object?o)?throws?HibernateException?{
????????return?o.hashCode();
????}
????public?boolean?isMutable()?{
????????return?true;
????}
????public?Object?nullSafeGet(ResultSet?rs,?String[]?names,?Object?o)?throws?HibernateException,?SQLException?{
????????String?email?=?rs.getString(names[0]);
????????if(email?==?null)return?null;
????????int?index?=?email.indexOf("@");
????????if(index?<?0)throw?new?RuntimeException();
????????return?new?Email(email.substring(0,index),email.substring(index+1));
????}
????public?void?nullSafeSet(PreparedStatement?ps,?Object?o,?int?index)?throws?HibernateException,?SQLException?{
????????if(o?==?null?)ps.setNull(index,?Types.VARCHAR);
????????else{
????????????Email?e?=?(Email)o;
????????????if(e.getDomain()?==?null?||?e.getUsername()?==?null)ps.setNull(index,?Types.VARCHAR);
????????????else{
????????????????String?email?=?e.getUsername()?+?"@"?+?e.getDomain();
????????????????ps.setString(index,?email);
????????????}
????????}
????????
????}
????public?Object?replace(Object?arg0,?Object?arg1,?Object?arg2)?throws?HibernateException?{
????????return?null;
????}
????public?Class?returnedClass()?{
????????return?Email.class;
????}
????public?int[]?sqlTypes()?{
????????int[]?typeList?=?{Types.VARCHAR};
????????return?typeList;
????}
}
?? ?最后配置下 email 屬性:
<property name="email" type="org.prague.domain.util.EmailUserType" column="email"></property>
?? ?相比于Gedner,Email是一個可變類(如果想將其變為不可變類,只需要去掉屬性的set方法),因此EmailUserType中的equals要用到Email的equals(hashCode())方法,而deepCopy(Object o) 要做到是深拷貝,否則即便Email屬性內容改變,由于Hibernate緩存中的快照指向的對象不變,在update時可能不起作用(在指定了dynamic-update屬性的清況下)。
?? ?