PC的blog

          Finding... Thinking... Solving...

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            9 Posts :: 0 Stories :: 54 Comments :: 0 Trackbacks
          前段時(shí)間寫了本書《Hibernate 3和Java Persistence API 程序開(kāi)發(fā)從入門到精通》,書中著重介紹了在Hibernate/JPA中使用Annotation。最近有讀者來(lái)信詢問(wèn)UserType,再加上最近看到有的人在項(xiàng)目中濫用Hibernate的user type,想在這里說(shuō)幾句。

          使用UserType首先要弄清楚它的目的。大家知道Hibernate解決的主要是對(duì)象數(shù)據(jù)庫(kù)阻抗失衡的問(wèn)題,也就是如何將一個(gè)或多個(gè)對(duì)象保存到一個(gè)或多個(gè)數(shù)據(jù)庫(kù)表格中。這其中有很多方法,其實(shí)大部分情況下采用@Embeddable和@Embedded就可以解決問(wèn)題了,只有嵌入對(duì)象方式無(wú)法滿足要求時(shí),或者是Hibernate默認(rèn)的持久化方式無(wú)法滿足要求時(shí),才應(yīng)該考慮UserType。總之記住一個(gè)原則,不到山窮水盡,不要輕易使用UserType。還有一個(gè)要慎重考慮使用UserType的原因是:一旦采用了UserType,你的項(xiàng)目就脫離了JPA,而直接和Hibernate耦合在一起了。

          擴(kuò)展UserType主要分為兩種:
          1. immutable
          2. mutable
          今天我先舉個(gè)immutable的例子。

          Java 5提出了一個(gè)新的enum類,JPA提供的標(biāo)準(zhǔn)方法是保存enum的name或者是ordinal。這種默認(rèn)方式能夠滿足新開(kāi)發(fā)的項(xiàng)目,但是對(duì)于一些老項(xiàng)目翻新并不一定適用。下面我們來(lái)看一個(gè)例子:

          public class Status {

              
          public static final int ACTIVATED = 5;
              
          public static final int DEACTIVATED = 6;
          }

          這個(gè)是在java5之前常用的常量定義方法,老項(xiàng)目數(shù)據(jù)庫(kù)里面已經(jīng)保存了很多的5啊6的。現(xiàn)在要把Status改寫成enum,而且不希望修改數(shù)據(jù)庫(kù)中已有的數(shù)據(jù),怎么做?第一反應(yīng),status enum可以這么寫:

          public enum Status {
                  ACTIVATED,
                  DEACTIVATED;
          }

          持久化enum的name屬性是肯定不用考慮了,ordinal屬性呢?這里要保存的可是5和6,而Status enum只有兩個(gè)實(shí)體,他們的ordinal只是0和1。而且項(xiàng)目中還會(huì)有其他很多類似的常量類需要改寫成enum,JPA的默認(rèn)方式無(wú)法完成任務(wù),這時(shí)候可以開(kāi)始考慮使用UserType了。

          先定義一個(gè)接口,這樣可以使用一個(gè)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;
              }

          }

          注意這里每個(gè)enum都必須有兩個(gè)static方法,這些方法名必須在所有的enum中保持一致。List()方法是為了方便獲取所有的Status常量,例如在用戶界面通過(guò)ComboBox展示,findById()方法是為了通過(guò)給定Id獲得對(duì)應(yīng)的Enum實(shí)例。其中findById()方法參數(shù)一定要是Integer,原因后面會(huì)講到。

          下面編寫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()};
              }


          }

          我們的這個(gè)UserType是要支持實(shí)現(xiàn)DescriptionID的各種不同的enum,而enum是沒(méi)法繼承的。所以我們需要用戶給出具體的參數(shù),以進(jìn)一步確定到底是哪個(gè)enum類。這也就導(dǎo)致了,我們的這個(gè)類需要實(shí)現(xiàn)ParameterizedType接口。

          由于enum類本身是immutable的,所以這個(gè)UserType的實(shí)現(xiàn)類相對(duì)比較簡(jiǎn)單,主要的兩個(gè)方法是
          nullSafeGet和nullSafeSet。在nullSaftGet中我們使用Java Reflection并借助用戶給出的enum類參數(shù)直接調(diào)用該enum類的findById()方法,這樣我們就可以使用數(shù)據(jù)庫(kù)中的integer找到對(duì)應(yīng)的enum實(shí)例。注意,由于使用了Java Reflection,所以findById()方法參數(shù)必須是Integer而非int。 在nullSafeSet中,我們則通過(guò)DescriptionID接口直接獲取enum實(shí)例的id屬性,并且將它保存到數(shù)據(jù)庫(kù)中去。

          最后看看怎么使用這個(gè)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時(shí)使用的parameter,"class"參數(shù)是我們自己定義的,該參數(shù)為DescriptionIDUserType提供了具體的enum類。前面已經(jīng)講過(guò)了,DescriptionIDUserType就是在運(yùn)行時(shí)態(tài)利用這個(gè)參數(shù)自定義enum與數(shù)據(jù)庫(kù)之間的持久化邏輯。

          使用這個(gè)UserType之后,我們就可以在確保數(shù)據(jù)庫(kù)數(shù)據(jù)不變的情況下,成功地將類型不保險(xiǎn)的常量類改寫成enum,而且這個(gè)UserType支持所有實(shí)現(xiàn)了
          DescriptionID接口的enum類。

          類似的情況朋友們可以自由發(fā)揮了。

          關(guān)于Annotation和Usertype的相關(guān)知識(shí)請(qǐng)參考我寫的《Hibernate 3和Java Persistence API 程序開(kāi)發(fā)從入門到精通》





          聲明:本文版權(quán)歸作者所有,如需轉(zhuǎn)載請(qǐng)注明出處。

          posted on 2008-03-21 20:14 polygoncell 閱讀(2973) 評(píng)論(4)  編輯  收藏

          Feedback

          # re: Hibernate user type 2008-03-21 20:22 333333333
          人家用你管得著嗎?  回復(fù)  更多評(píng)論
            

          # re: Hibernate user type 2008-03-21 21:57 ci
          不錯(cuò)...  回復(fù)  更多評(píng)論
            

          # re: Hibernate user type 2008-03-21 22:41 葛京
          謝謝捧場(chǎng),能用得上就好。  回復(fù)  更多評(píng)論
            

          # re: Hibernate user type 2008-03-29 00:19 polygoncell

          本書的命題是“入門和精通”,網(wǎng)上提供的章節(jié)僅僅是入門級(jí)別的內(nèi)容,是為那些完全沒(méi)有Hibernate基礎(chǔ)的同學(xué)準(zhǔn)備的。 那些已經(jīng)了解Hibernate的同學(xué)一定會(huì)覺(jué)得這些章節(jié)很乏味,這是很正常的,因?yàn)槟銈円呀?jīng)掌握了這些入門級(jí)別的內(nèi)容,再看一遍,自然乏味。但是請(qǐng)你們?yōu)槟切膩?lái)沒(méi)有接觸過(guò)Hibernate的同學(xué)考慮一下,他們非常需要一個(gè)相對(duì)淺顯易懂的臺(tái)階來(lái)幫助他們“入門”。這就是我撰寫前幾章入門內(nèi)容的初衷。

          對(duì)于那些已經(jīng)了解Hibernate的朋友們,請(qǐng)你們靜下心來(lái)閱讀后面深入內(nèi)核的章節(jié),在這些章節(jié)中,我是從構(gòu)架的角度講解了Hibernate的幾個(gè)主要的模塊,舉例印證,圖文并茂,大部分內(nèi)容源于實(shí)際項(xiàng)目。如果通讀完全書,還有朋友認(rèn)為這本書“不怎么樣”,那么我作為這本書的作者,在這里誠(chéng)心誠(chéng)意的期盼著你們的寶貴意見(jiàn),對(duì)于正確的意見(jiàn),我將會(huì)在本書的后續(xù)版本中加以采納。

          不論如何,非常感謝大家對(duì)本書的關(guān)注。  回復(fù)  更多評(píng)論
            


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 修水县| 肥乡县| 云阳县| 梅州市| 苍山县| 漳平市| 逊克县| 罗江县| 贞丰县| 靖边县| 敦煌市| 平山县| 克东县| 铅山县| 白河县| 琼海市| 松滋市| 胶南市| 鸡东县| 尚义县| 祥云县| 灵武市| 双桥区| 邛崃市| 林口县| 合阳县| 绵竹市| 鞍山市| 凤庆县| 泗洪县| 靖西县| 五华县| 江华| 铁力市| 巩义市| 离岛区| 乐平市| 海阳市| 始兴县| 临湘市| 临邑县|