【原創】再談基於註解運行時動態ORM映射

   上一篇貼出《基於註解運行時動態ORM映射》的方案,到底哪些地方須要用?又該怎麼用呢?!我想你會有這樣的疑問,其實不瞞你說,我也有!呵呵~css

再談一把,就成爲「必須的」~  所以,本文主要介紹動態ORM映射適用具體場景以及詳細實現方案。html

   上篇文章提出來如何運行時動態修改註解的解決方案,它在那裏好好的,咱們幹嗎沒事修改它?到底有何企圖?它的應用場景又是什麼呢?讓咱們揭開它神祕的面紗吧~ 個人乖乖!java

   在面向對象(OO)軟件產品設計中,設計者們在面對諸如MySQL、MSSQL、ORACLE這類由關係數據庫組成的關係數據庫管理系統(RDBMS)時,對象關係映射方案(ORM)逐漸成爲主流技術。ORM系統管理着關係數據模型與對象模型(Object Model)的關係,一般關係數據模型(NRDM)與對象模型一一對應,NRDM中的一張表的結構對應對象模型中一個實體類的結構,表中的字段則對應實體對象的屬性,表中的一行記錄又對應着一個實體對象(Entity Object)。從而,ORM系統的對象模型靈活的對接了關係數據庫管理系統,隔離了關係數據模型。開發人員無需關注關係數據模型與對象模型的矛盾,只需在數據訪問層(DAO)傳遞對象模型,ORM會智能地把對象模型匹配到具體的關係數據模型上,觸發關係數據庫管理系統進行數據訪問請求。現在業界也有不少成熟而且被普遍應用的ORM映射方案。好比Hibernate、MyBatis、Apache OJB等。它們面對處理對象模型相對固定的模式時,徹底可以智能的提供基於對象模型的ORM映射方案,由於它們將關係數據模型與對象模型的對應關係在ORM系統中進行了約定配置(聲明式配置),也就是所謂的靜態映射。然而,隨着企業應用不斷的發展,商業需求對ORM系統的映射方案再也不停留在靜態映射的層面上,面對海量用戶數據以及高訪問量的大型企業應用,許多系統採用了分庫分表的架構,部分應用還存在系統上線後期,客戶自定義字段的需求。若是一個系統根據商業需求採用了分表策略(eg.電子商務系統一般對用戶訂單相關表採用分表存儲),則意味着自身的ORM系統必須支持一個對象模型對應多個關係數據模型;針對後期客戶自定義字段也意味着須要修改ORM系統中的關係數據模型以及實體對象模型,甚至修改關係數據庫中表結構,遷移舊數據等繁瑣的工做。那麼此時傳統的ORM靜態映射方案就再也不那麼智能,這就須要提供一種動態映射的技術方案。親,示例案例以下,數據庫

1. 水平分表關係數據模型數據結構

    XX電子商務系統因爲考慮到系統交易信息量過大,對交易相關信息表採用了分表策略。例如,該系統關係數據庫中有默認有一張訂單(Order0)表,隨着交易數據量的增長逐漸產生了Order1,Order2,Order3… 它們的共同特徵是表數據結構相同,表名不一樣而已。這就是水平分表關係數據模型。隨着訂單表遞增式分表,該系統的ORM子系統將面臨着一個實體模型對應多個結構相同的關係數據模型。技術背景中已經提到,傳統ORM系統對對象關係映射是聲明式的配置方案,例如,實體模型Order配置映射關係數據模型爲Order0。那麼它在ORM系統中就只會映射到Order0這個數據模型。所以,它只能提供靜態的ORM映射。面對這種水平分表關係數據模型,就須要實體模型運行時正確匹配對應的關係數據模型。架構

   例如,當前業務須要獲取關係數據模型Order3。app

   具體實施流程:ide

   ①動態映射代理器攔截到動態映射請求;工具

   ②動態映射代理器轉發映射請求給動態映射管理器;this

   ③動態映射管理器解析映射參數,並驗證映射參數合法性;

   ④動態映射管理器將ORM系統中對象模型Order映射的Order0更新爲Order3;

   ⑤ORM系統基於動態映射後的對象模型Order對關係數據模型Order3進行數據訪問,並返回ORM系統處理結果。

2. 垂直分表關係數據模型

    若是說水平分表關係數據模型的處理方案屬於橫向動態映射策略,根據映射要求動態更新對象模型所映射的關係數據模型便可。而客戶自定義字段關係數據模型屬於縱向動態映射策略,它要求運行時更新對象模型中實體對象的屬性以及擴展關係數據模型中數據表的字段。從而,相對處理水平分表關係數據模型而言,還有擴展關係數據模型的步驟。所以,本質上它就是垂直分表關係數據模型。例如,XX門戶系統中,關係數據模型中有張用戶表(User)。根據系統前期需求調研,只需用戶對聯繫電話(Phone)的描述提供工做電話(WorkPhone)、移動電話(MobilePhone)便可。所以,User表中Phone的描述只有WorkPhone和MobilePhone兩個字段。然而系統上線後,客戶提出還須要家庭電話(HomePhone)。面對客戶提出的這種需求時,傳統ORM系統就須要爲了這個很細小的變化去修改對象模型,甚至遷移User表歷史數據,在User表中增長HomePhone字段。雖然這也是一種解決方案,可是若是之後客戶提出更多的自定義字段需求,那麼上面的解決方案無疑對開發人員而言簡直就是噩耗。

   具體實施流程:

   ①動態映射代理器攔截到動態映射請求;

   ②動態映射代理器轉發映射請求給動態映射管理器;

   ③動態映射管理器解析映射參數,並驗證映射參數合法性;

   ④動態映射管理器爲ORM系統中對象模型User添加

   HomePhone屬性,同時擴展User對應的關係數據模型。

   User關係數據模型結構以下:

   A .User關係數據模型: 

UserId

WorkPhone

MobilePhone

其餘字段。。。

   B.User擴展關係數據模型:

UserId

ExpandField

ExpandValue

IsDelete

  按照上圖B表所示結構,動態映射管理器將UserId、ExpandField、ExpandValue、IsDelete四個字段寫入User_Expand表中;

   ⑤動態映射管理器將擴展字段信息寫入User擴展關係數據模型中;

   ⑥傳統ORM系統將User原有數據信息寫入其關係數據模型中,最後返回ORM系統處理結果。

    從以上兩種示例案例咱們不難看出,這兩種需求對傳統靜態ORM映射方案提出了運行時動態映射的挑戰!咱們採用運行時動態更新予以化解,使其與傳統ORM靜態映射方案良好對接,如同爲ORM系統提供動態映射插件。從而,保證了系統「風格」一致性。須要說明的是更適用於POJO動態映射的範圍較小的狀況。(動態映射的分表關係數據模型以及擴展關係數據模型影響範圍就較小),若是整個對象須要動態映射不一樣結構的表,那就徹底不必了!卻是能夠作到,卻沒什麼意義。至關於一個POJO通吃~ 若是須要動態映射的範圍太大,你就須要考慮是不是你方案的問題了-- 有必要用憨包兒呢特(Hibernate)嗎?!通常來講映射的東西是配置性的,初始化時就定了。咱們動態映射已經違背常倫咯 搞太多的特殊化,仍是很差的! 所以後面的詳細實現方案我就只乖乖的動態映射表名,呵呵。。。

    下面我們來講說咋個特殊化哈~ 注意咯

        1、動態映射管理器-修改POJO的元兇

 1: /**
 2:  * 對象池工具類
 3:  * 
 4:  * 目前提供ORM動態映射解決方案
 5:  * 
 6:  * @author andy.zheng
 7:  * @since 2012.09.25 15:55 PM
 8:  * @vesion 1.0
 9:  * 
 10:  */
 11: public class ClassPoolUtils {
 12: 
 13: 
 14:     /**
 15:  * 運行時動態ORM表映射
 16:  * 
 17:  * 
 18:  * @param entityClassName 待映射的實體全限定類名
 19:  * @param tableName 待映射的表名
 20:  * @return 映射後的類對象
 21:  */
 22:     public static Class<?> tableMapping(String entityClassName, String tableName){
 23:         Class<?> c = null;
 24: 
 25:         if(StringUtils.isEmpty(entityClassName) || StringUtils.isEmpty(tableName)){
 26:             throw new IllegalArgumentException("The mapping parameter is invalid!");
 27:         }
 28: 
 29:         try {
 30:             ClassPool classPool = ClassPool.getDefault();
 31:             classPool.appendClassPath(new ClassClassPath(ClassPoolUtils.class));
 32:             classPool.importPackage("javax.persistence");
 33:             CtClass clazz = classPool.get(entityClassName);
 34:             clazz.defrost();
 35:             ClassFile classFile = clazz.getClassFile();
 36: 
 37:             ConstPool constPool = classFile.getConstPool();
 38:             Annotation tableAnnotation = new Annotation("javax.persistence.Table", constPool);
 39:             tableAnnotation.addMemberValue("name", new StringMemberValue(tableName, constPool));
 40:             // 獲取運行時註解屬性
 41:             AnnotationsAttribute attribute = (AnnotationsAttribute)classFile.getAttribute(AnnotationsAttribute.visibleTag);
 42:             attribute.addAnnotation(tableAnnotation);
 43:             classFile.addAttribute(attribute);
 44:             classFile.setVersionToJava5();
 45:             //clazz.writeFile();
 46: 
 47:             //TODO 當前ClassLoader中必須還沒有加載該實體。(同一個ClassLoader加載同一個類只會加載一次)
 48:             //c = clazz.toClass();
 49:             EntityClassLoader loader = new EntityClassLoader(ClassPoolUtils.class.getClassLoader());
 50:             c = clazz.toClass(loader , null);
 51:         } catch (Exception e) {
 52:             e.printStackTrace();
 53:         }
 54: 
 55:         return c;
 56:     }
 57:  
 58:     public static void main(String[] args) {
 59:         Class<?> clazz = ClassPoolUtils.tableMapping("com.andy.model.order.Order", "order1");
 60:         System.out.println("修改後的@Table: " + clazz.getAnnotation(Table.class));
 61:     }
 62: }

2、PO對象加載器

 1: /**
 2:  * 實體類加載器
 3:  * 
 4:  * 該加載器主要用於運行時動態修改實體後,從新裝載實體
 5:  * 
 6:  * @author andy.zheng
 7:  * @since 2012.09.25 16:18 PM
 8:  * @vesion 1.0
 9:  *
 10:  */
 11: public class EntityClassLoader extends ClassLoader {
 12: 
 13:     private ClassLoader parent;
 14: 
 15:     public EntityClassLoader(ClassLoader parent){
 16:         this.parent = parent;
 17:     }
 18: 
 19:     @Override
 20:     public Class<?> loadClass(String name) throws ClassNotFoundException {
 21:         return this.loadClass(name, false);
 22:     }
 23: 
 24:     @Override
 25:     protected synchronized Class<?> loadClass(String name, boolean resolve)
 26:             throws ClassNotFoundException {
 27:         Class<?> clazz = this.findLoadedClass(name);
 28:         if(null != parent){
 29:             clazz = parent.loadClass(name);
 30:         }
 31:         if(null == clazz){
 32:             this.findSystemClass(name);
 33:         }
 34: 
 35:         if(null == clazz){
 36:             throw new ClassNotFoundException();
 37:         }
 38:         if(null != clazz && resolve){
 39:             this.resolveClass(clazz);
 40:         }
 41: 
 42:         return clazz;
 43:     }
 44: 
 45: 
 46: 
 47:  
 48:     /**
 49:  * @param args
 50:  */
 51:     public static void main(String[] args) {
 52:  
 53:     }
 54:  
 55: }

3、適配傳統ORM

     將最新映射對象交給Hibernate吧~ 固然這個東東固然在Dao層哈,須要覆蓋hibernate初始化默認加載的映射對象。你能夠把它正在諸如BaseHiberanteDao中,在須要動態映射表名的時候,先調它一把,而後再寫你的HQL.固然若是接口統一的話,你也能夠玩高級一點的。至於咋個高級法,參考動態代理相關的文章。(爲須要動態映射的接口代理一下,悄無聲息的動態映射一把!!!可謂是神不知鬼不覺~),

 1:    /**
 2:  * 運行時動態ORM表映射
 3:  * 
 4:  * @param tableMapping 映射集合 
 5:  * key - 待映射的表名 value - 待映射的實體對象
 6:  */
 7:     @SuppressWarnings("unused")
 8:     protected void tableMapping(Map<String, Class<?>> tableMapping){
 9:         Assert.notEmpty(tableMapping , "The mapping parameter is empty!");
 10:         for (String tableName : tableMapping.keySet()) {
 11:             Class<?> entityClass = tableMapping.get(tableName);
 12:             String className = entityClass.getName();
 13:             ClassMetadata metadata = this.getSessionFactory().getClassMetadata(className);
 14:             Class<?> mappedClass = metadata.getMappedClass();
 15:             mappedClass = ClassPoolUtils.tableMapping(className, tableName);
 16:         }
 17:     }

4、調用示例

 1: public Page<OrderDetail> getList(int currentPage , int pageSize){
 2:    this.tableMapping(new HashMap(){
 3:       {
 4:                 this.put("orderdetail1", OrderDetail.class);
 5:        }
 6:    });
 7:   Page<OrderDetail> page = this.<OrderDetail>pagingList("", currentPage , pageSize);
 8:   Assert.notEmpty(page.getItems());
 9:   return page;
 10: }

執行HQL:

 1: Hibernate: select count(*) as col_0_0_ from OrderDetail orderdetai0_
 2: Hibernate: select orderdetai0_.id as id15_, orderdetai0_.docid as docid15_, orderdetai0_.ErrorDesc as ErrorDesc15_, orderdetai0_.insertedtime as inserted3_15_, orderdetai0_.OrderID as OrderID15_, orderdetai0_.ordernum as ordernum15_, orderdetai0_.SegmentsIDs as Segments5_15_, orderdetai0_.selltype as selltype15_, orderdetai0_.status as status15_ from OrderDetail orderdetai0_
相關文章
相關標籤/搜索