【原创】再谈基于注解运行时动态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_
相关文章
相关标签/搜索