【原创】再谈基于注解运行时动态ORM映射

隐身守侯 提交于 2019-12-09 19:53:30

   上一篇贴出《基于注解运行时动态ORM映射》的方案,到底哪些地方需要用?又该怎么用呢?!我想你会有这样的疑问,其实不瞒你说,我也有!呵呵~

再谈一把,就成为“必须的”~  因此,本文主要介绍动态ORM映射适用具体场景以及详细实现方案。

   上篇文章提出来如何运行时动态修改注解的解决方案,它在那里好好的,我们干嘛没事修改它?到底有何企图?它的应用场景又是什么呢?让我们揭开它神秘的面纱吧~ 我的乖乖!

   在面向对象(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。

   具体实施流程:

   ①动态映射代理器拦截到动态映射请求;

   ②动态映射代理器转发映射请求给动态映射管理器;

   ③动态映射管理器解析映射参数,并验证映射参数合法性;

   ④动态映射管理器将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)吗?!一般来说映射的东西是配置性的,初始化时就定了。我们动态映射已经违背常伦咯

搞太多的特殊化,还是不好的! 因此后面的详细实现方案我就只乖乖的动态映射表名,呵呵。。。

    下面咱们来说说咋个特殊化哈~ 注意咯

        一、动态映射管理器-修改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:  }

二、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:  }

三、适配传统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:      }

四、调用示例

   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_
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!