PC的blog

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

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

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

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

          Java 5提出了一個新的enum類,JPA提供的標準方法是保存enum的name或者是ordinal。這種默認方式能夠滿足新開發的項目,但是對于一些老項目翻新并不一定適用。下面我們來看一個例子:

          public class Status {

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

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

          public enum Status {
                  ACTIVATED,
                  DEACTIVATED;
          }

          持久化enum的name屬性是肯定不用考慮了,ordinal屬性呢?這里要保存的可是5和6,而Status enum只有兩個實體,他們的ordinal只是0和1。而且項目中還會有其他很多類似的常量類需要改寫成enum,JPA的默認方式無法完成任務,這時候可以開始考慮使用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獲得對應的Enum實例。其中findById()方法參數一定要是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是要支持實現DescriptionID的各種不同的enum,而enum是沒法繼承的。所以我們需要用戶給出具體的參數,以進一步確定到底是哪個enum類。這也就導致了,我們的這個類需要實現ParameterizedType接口。

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

          最后看看怎么使用這個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"參數是我們自己定義的,該參數為DescriptionIDUserType提供了具體的enum類。前面已經講過了,DescriptionIDUserType就是在運行時態利用這個參數自定義enum與數據庫之間的持久化邏輯。

          使用這個UserType之后,我們就可以在確保數據庫數據不變的情況下,成功地將類型不保險的常量類改寫成enum,而且這個UserType支持所有實現了
          DescriptionID接口的enum類。

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

          關于Annotation和Usertype的相關知識請參考我寫的《Hibernate 3和Java Persistence API 程序開發從入門到精通》





          聲明:本文版權歸作者所有,如需轉載請注明出處。

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

          Feedback

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

          # re: Hibernate user type 2008-03-21 21:57 ci
          不錯...  回復  更多評論
            

          # re: Hibernate user type 2008-03-21 22:41 葛京
          謝謝捧場,能用得上就好。  回復  更多評論
            

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

          本書的命題是“入門和精通”,網上提供的章節僅僅是入門級別的內容,是為那些完全沒有Hibernate基礎的同學準備的。 那些已經了解Hibernate的同學一定會覺得這些章節很乏味,這是很正常的,因為你們已經掌握了這些入門級別的內容,再看一遍,自然乏味。但是請你們為那些從來沒有接觸過Hibernate的同學考慮一下,他們非常需要一個相對淺顯易懂的臺階來幫助他們“入門”。這就是我撰寫前幾章入門內容的初衷。

          對于那些已經了解Hibernate的朋友們,請你們靜下心來閱讀后面深入內核的章節,在這些章節中,我是從構架的角度講解了Hibernate的幾個主要的模塊,舉例印證,圖文并茂,大部分內容源于實際項目。如果通讀完全書,還有朋友認為這本書“不怎么樣”,那么我作為這本書的作者,在這里誠心誠意的期盼著你們的寶貴意見,對于正確的意見,我將會在本書的后續版本中加以采納。

          不論如何,非常感謝大家對本書的關注。  回復  更多評論
            


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 肇源县| 钟祥市| 多伦县| 惠来县| 西宁市| 全州县| 通河县| 九龙坡区| 任丘市| 开江县| 中方县| 伊通| 赫章县| 嘉善县| 樟树市| 渝北区| 阿坝| 哈巴河县| 固始县| 治县。| 义马市| 上杭县| 文成县| 永靖县| 瑞丽市| 广宁县| 福建省| 湟源县| 绍兴市| 甘孜县| 鹿泉市| 建始县| 西华县| 贵阳市| 响水县| 拉孜县| 鄂托克旗| 临沂市| 邵阳市| 亚东县| 获嘉县|