• 分享公司DAO层数据库结果映射到对象的方法


    主题

      前面写过一篇文章,分享了公司是怎么动态封装SQL查询条件的(http://www.cnblogs.com/abcwt112/p/5874401.html).

      里面提到数据库查询结果二维数组最后是封装到List<DTO>里的.即把数据库结果集封装成了对象集合.就像hibernate那样...那么公司是怎么做到的呢,这就是这篇文章的主题.

    原理

      类似于其他持久成框架,数据库结果要转化成对象,一定会有个映射规则.比如Mybatis里的Mapper文件用XML去描述映射规则,JPA则用注解@Column去描述规则.

      公司也有自己的描述方法,参考了JPA,同样也是用注解的方式.有了规则,只要将数据库查询结果应用下规则即可.

    规则

     1 @Target({ ElementType.FIELD })
     2 @Retention(RetentionPolicy.RUNTIME)
     3 @Inherited
     4 @Documented
     5 public @interface DbNameColumn {
     6 
     7 
     8     String fieldName() default "";
     9 
    10 
    11     Class<? extends Converter> converter() default Converter.class;
    12 }
     1 @Target({ ElementType.FIELD })
     2 @Retention(RetentionPolicy.RUNTIME)
     3 @Inherited
     4 @Documented
     5 public @interface DbColumn {
     6 
     7     int index() default -1;
     8 
     9     Class<? extends Converter> converter() default Converter.class;
    10 
    11 }

    有2种规则,前面那种是通过结果集的column name(DbNameColumn.fieldName其实叫columnName似乎更好)与对象字段名做关联,下面那种规则是通过结果集的column index(DbColumn.index)与对象字段名做映射.

    很明显,通过index来映射是不好的方式,因为当多个service共用一套规则的时候,如果某一个service想修改SQL在数据库结果中额外插入一列的话是做不到的,因为会影响其他映射的index.而DbColumnName这套映射规则就可以.因为这个依赖于数据库列的别名,而不是顺序.

    converter是org.apache.commons.beanutils.Converter接口的实现类,这里依赖apache的包,可以自己指定Converter.虽然实际使用中没人会指定Converter.因为用公司这种NativeSQL就是为了省力,不需为了每个查询都写个Entity实体去映射数据库,而是定义一个DTO去映射N种相同列(或者部分列)查询结果的不同SQL..如果这里要自己再写个Converter的话还不如写个Entity简单.

    实现规则

    有了规则就只要实现这个规则就可以了

      1 package cn.com.servyou.framework.jpa;
      2 
      3 import java.lang.reflect.Field;
      4 import java.lang.reflect.InvocationTargetException;
      5 import java.util.ArrayList;
      6 import java.util.List;
      7 import java.util.Map;
      8 
      9 import org.apache.commons.beanutils.PropertyUtils;
     10 import org.slf4j.Logger;
     11 import org.slf4j.LoggerFactory;
     12 
     13 import cn.com.servyou.framework.annotation.AnnotationUtils;
     14 import cn.com.servyou.framework.beans.converter.ConverterUtilsWrapper;
     15 import cn.com.servyou.framework.exception.SystemException;
     16 
     17 
     18 public final class DbColumnMapper {
     19 
     20     /**
     21      * The Constant LOGGER.
     22      */
     23     private static final Logger LOGGER = LoggerFactory.getLogger(DbColumnMapper.class);
     24 
     25     /**
     26      * Instantiates a new db column mapper.
     27      */
     28     private DbColumnMapper() {
     29     }
     30 
     31     /**
     32      * The list must contains the object array, which is the row. Each object in
     33      * the array is the column element.
     34      * 
     35      * @param <T>
     36      *            the generic type
     37      * @param queryResult
     38      *            the query result
     39      * @param targetClass
     40      *            the target class
     41      * @return the list
     42      */
     43     public static <T> List<T> resultMapping(List<?> queryResult, Class<? extends T> targetClass) {
     44 
     45         return innerMapping(queryResult, targetClass);
     46     }
     47 
     48     /**
     49      * Checks if is named mapping.
     50      * 
     51      * @param targetClass
     52      *            the target class
     53      * @return true, if is named mapping
     54      */
     55     public static boolean isNamedMapping(Class<?> targetClass) {
     56 
     57         List<Field> indexAnnotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbColumn.class, targetClass);
     58         List<Field> nameAnnotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbNameColumn.class,
     59                 targetClass);
     60         if (!indexAnnotationFieldList.isEmpty() && nameAnnotationFieldList.isEmpty()) {
     61             return false;
     62         } else if (!nameAnnotationFieldList.isEmpty() && indexAnnotationFieldList.isEmpty()) {
     63             return true;
     64         } else {
     65             throw new SystemException("不允许使用混合的数据库mapping", SystemException.INCONSISTENCE_EXCEPTION);
     66         }
     67     }
     68 
     69     /**
     70      * Inner mapping.
     71      * 
     72      * @param <D>
     73      *            the generic type
     74      * @param queryResult
     75      *            the query result
     76      * @param targetClass
     77      *            the target class
     78      * @return the list
     79      */
     80     @SuppressWarnings("unchecked")
     81     private static <D> List<D> innerMapping(List<?> queryResult, Class<? extends D> targetClass) {// NOSONAR
     82 
     83         List<D> resultList = new ArrayList<D>();
     84 
     85         if (!isNamedMapping(targetClass)) {
     86             List<Field> annotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbColumn.class, targetClass);
     87             for (Object obj : queryResult) {
     88                 try {// NOSONAR
     89                     D targetDto = targetClass.newInstance();
     90                     Object[] row = (Object[]) obj;
     91                     for (Field field : annotationFieldList) {
     92                         DbColumn dbColumn = field.getAnnotation(DbColumn.class);
     93                         if (dbColumn.converter() != null && !dbColumn.converter().isInterface()) {
     94                             ConverterUtilsWrapper.register(dbColumn.converter().newInstance(), field.getType());
     95                         }
     96                         try {// NOSONAR
     97                             if (PropertyUtils.isWriteable(targetDto, field.getName())) {
     98                                 if (dbColumn.index() >= 0 && dbColumn.index() < row.length) {
     99                                     // BUG#1865
    100                                     int columnIndex = field.getAnnotation(DbColumn.class).index();
    101 
    102                                     PropertyUtils.setProperty(targetDto, field.getName(),
    103                                             ConverterUtilsWrapper.convert(row[columnIndex], field.getType()));
    104                                 } else {
    105                                     throw new SystemException("类" + targetClass.getName() + "的字段" + field.getName()
    106                                             + "没有正确设置字段索引,应设置小于" + row.length + "的索引值", new Exception("类"
    107                                             + targetClass.getName() + "的字段" + field.getName() + "没有正确设置字段索引,应设置小于"
    108                                             + row.length + "的索引值"), SystemException.REQUEST_EXCEPTION);
    109                                 }
    110                             }
    111 
    112                         } catch (InvocationTargetException e) {
    113                             LOGGER.error("[Error!]", e);
    114                         } catch (NoSuchMethodException e) {
    115                             LOGGER.error("[Error!]", e);
    116                         }
    117                     }
    118                     resultList.add(targetDto);
    119                 } catch (InstantiationException e1) {
    120                     LOGGER.error("[Error!]", e1);
    121                 } catch (IllegalAccessException e1) {
    122                     LOGGER.error("[Error!]", e1);
    123                 }
    124 
    125             }
    126             return resultList;
    127         } else {
    128             List<Field> annotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbNameColumn.class,
    129                     targetClass);
    130             for (Object obj : queryResult) {
    131                 try {// NOSONAR
    132                     D targetDto = targetClass.newInstance();
    133                     Map<String, ?> row = (Map<String, ?>) obj;
    134                     for (Field field : annotationFieldList) {
    135                         DbNameColumn dbColumn = field.getAnnotation(DbNameColumn.class);
    136                         if (dbColumn.converter() != null && !dbColumn.converter().isInterface()) {
    137                             ConverterUtilsWrapper.register(dbColumn.converter().newInstance(), field.getType());
    138                         }
    139                         try {// NOSONAR
    140                             if (PropertyUtils.isWriteable(targetDto, field.getName())) {
    141                                 String colName = dbColumn.fieldName();
    142                                 PropertyUtils.setProperty(targetDto, field.getName(),
    143                                         ConverterUtilsWrapper.convert(row.get(colName.toUpperCase()), field.getType()));
    144                             }
    145                         } catch (InvocationTargetException e) {
    146                             LOGGER.error("[Error!]", e);
    147                         } catch (NoSuchMethodException e) {
    148                             LOGGER.error("[Error!]", e);
    149                         }
    150                     }
    151                     resultList.add(targetDto);
    152                 } catch (InstantiationException e1) {
    153                     LOGGER.error("[Error!]", e1);
    154                 } catch (IllegalAccessException e1) {
    155                     LOGGER.error("[Error!]", e1);
    156                 }
    157             }
    158             return resultList;
    159         }
    160     }
    161 
    162 }
    View Code

    以上是完整的映射规则的实现.类很长,但是核心代码不多..总共就2条分支,一条是index来映射,一条是column name来映射,分别对应前面的2种规则.

    index那种映射真的不好.所以我就分享下DbNameColumn那套映射规则.核心代码如下:

     1 List<Field> annotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbNameColumn.class,
     2                     targetClass);
     3             for (Object obj : queryResult) {
     4                 try {// NOSONAR
     5                     D targetDto = targetClass.newInstance();
     6                     Map<String, ?> row = (Map<String, ?>) obj;
     7                     for (Field field : annotationFieldList) {
     8                         DbNameColumn dbColumn = field.getAnnotation(DbNameColumn.class);
     9                         if (dbColumn.converter() != null && !dbColumn.converter().isInterface()) {
    10                             ConverterUtilsWrapper.register(dbColumn.converter().newInstance(), field.getType());
    11                         }
    12                         try {// NOSONAR
    13                             if (PropertyUtils.isWriteable(targetDto, field.getName())) {
    14                                 String colName = dbColumn.fieldName();
    15                                 PropertyUtils.setProperty(targetDto, field.getName(),
    16                                         ConverterUtilsWrapper.convert(row.get(colName.toUpperCase()), field.getType()));
    17                             }
    18                         } catch (InvocationTargetException e) {
    19                             LOGGER.error("[Error!]", e);
    20                         } catch (NoSuchMethodException e) {
    21                             LOGGER.error("[Error!]", e);
    22                         }
    23                     }
    24                     resultList.add(targetDto);
    25                 } catch (InstantiationException e1) {
    26                     LOGGER.error("[Error!]", e1);
    27                 } catch (IllegalAccessException e1) {
    28                     LOGGER.error("[Error!]", e1);
    29                 }
    30             }

    实现细节:

    1.数据库查询结果是List<Map>这种形式,为什么不是List<Object[]>或者其他形似呢?这个是hibernate执行原生SQL的时候可以选择.用Map的话和DbNameColumn映射比较简单,List的话和DbColumn映射比较简单.

    2.我们知道要映射的对象的class以后可以用反射生成空的对象,同样用反射得到class里标注了DbNameColumn注解的所有Field,得到这些Field上面的DbNameColumn注解里需要映射到的数据库Column name.

    3.从Map里找那一行数据库记录的值,设置到对象里就行,这里用的是org.apache.commons.beanutils.PropertyUtils.

    这样就可以将数据库结果映射到对象了.

    思考小结

    用1句话来表述实现的话就是:

    通过注解描述字段与数据库结果集列名的映射关系,通过反射和PropertyUtils来实现映射.

    公司的这套实现方案算是对Spring Data映射的一种补充吧..

    1.当不想写很多entity(比如太多代码表或者参数表) 或者

    2.为很多很多相同或相似结构结果的SQL(比如不同表查询出相同树形结果的结果)提供一个统一的映射方案

    的话是一种不错的选择..

    但是似乎也有一些地方是可以优化的,比如:

    这里对象设值用的是apache的PropertyUtils.此外公司对象之间转化有自己定义的ConverterUtil(底层用cglib的BeanCopier实现),再或者公司框架都是基于Spring的,Spring也有自己的类型转化方法.

    这里是不是可以统一呢? Spring框架源码我们不会去修改,那我们能不能把我们的实现合并到Spring的实现中呢?

    比如前面转化的时候的2行代码:

    ConverterUtilsWrapper.register(dbColumn.converter().newInstance(), field.getType());

    ConverterUtilsWrapper.convert(row.get(colName.toUpperCase());

    这是不是似曾相识?!

    Spring本身已经自带了类型转化的方法,提供了很多基本类型的转化,那么我们可以使用他的...除此之外的业务相关特有的转化再添加到这个service里就可以了..

    不过不管怎么说...这只是实现细节的不同.....现在的实现也是一种不错的实现...

  • 相关阅读:
    数论模板
    HZNU_TI1050 训练实录
    2019 ICPC Asia Xuzhou Regional
    ICPC 2019-2020 North-Western Russia Regional Contest
    2019 ICPC Asia Yinchuan Regional
    2019-2020 ICPC, Asia Jakarta Regional Contest
    The 2019 China Collegiate Programming Contest Harbin Site
    2019-2020 ICPC, NERC, Southern and Volga Russian Regional Contest
    Educational Codeforces Round 75
    2018-2019 ACM-ICPC, Asia Dhaka Regional Contest
  • 原文地址:https://www.cnblogs.com/abcwt112/p/6069619.html
Copyright © 2020-2023  润新知