分享公司DAO层数据库结果映射到对象的方法
主题
前面写过一篇文章,分享了公司是怎么动态封装SQL查询条件的(http://www.cnblogs.com/abcwt112/p/5874401.html).
里面提到数据库查询结果二维数组最后是封装到List<DTO>里的.即把数据库结果集封装成了对象集合.就像hibernate那样...那么公司是怎么做到的呢,这就是这篇文章的主题.
原理
类似于其他持久成框架,数据库结果要转化成对象,一定会有个映射规则.比如Mybatis里的Mapper文件用XML去描述映射规则,JPA则用注解@Column去描述规则.
公司也有自己的描述方法,参考了JPA,同样也是用注解的方式.有了规则,只要将数据库查询结果应用下规则即可.
规则
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DbNameColumn { String fieldName() default ""; Class<? extends Converter> converter() default Converter.class;
}
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DbColumn { int index() default -1; Class<? extends Converter> converter() default Converter.class; }
有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简单.
实现规则
有了规则就只要实现这个规则就可以了
package cn.com.servyou.framework.jpa; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import cn.com.servyou.framework.annotation.AnnotationUtils;
import cn.com.servyou.framework.beans.converter.ConverterUtilsWrapper;
import cn.com.servyou.framework.exception.SystemException; public final class DbColumnMapper { /**
* The Constant LOGGER.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(DbColumnMapper.class); /**
* Instantiates a new db column mapper.
*/
private DbColumnMapper() {
} /**
* The list must contains the object array, which is the row. Each object in
* the array is the column element.
*
* @param <T>
* the generic type
* @param queryResult
* the query result
* @param targetClass
* the target class
* @return the list
*/
public static <T> List<T> resultMapping(List<?> queryResult, Class<? extends T> targetClass) { return innerMapping(queryResult, targetClass);
} /**
* Checks if is named mapping.
*
* @param targetClass
* the target class
* @return true, if is named mapping
*/
public static boolean isNamedMapping(Class<?> targetClass) { List<Field> indexAnnotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbColumn.class, targetClass);
List<Field> nameAnnotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbNameColumn.class,
targetClass);
if (!indexAnnotationFieldList.isEmpty() && nameAnnotationFieldList.isEmpty()) {
return false;
} else if (!nameAnnotationFieldList.isEmpty() && indexAnnotationFieldList.isEmpty()) {
return true;
} else {
throw new SystemException("不允许使用混合的数据库mapping", SystemException.INCONSISTENCE_EXCEPTION);
}
} /**
* Inner mapping.
*
* @param <D>
* the generic type
* @param queryResult
* the query result
* @param targetClass
* the target class
* @return the list
*/
@SuppressWarnings("unchecked")
private static <D> List<D> innerMapping(List<?> queryResult, Class<? extends D> targetClass) {// NOSONAR List<D> resultList = new ArrayList<D>(); if (!isNamedMapping(targetClass)) {
List<Field> annotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbColumn.class, targetClass);
for (Object obj : queryResult) {
try {// NOSONAR
D targetDto = targetClass.newInstance();
Object[] row = (Object[]) obj;
for (Field field : annotationFieldList) {
DbColumn dbColumn = field.getAnnotation(DbColumn.class);
if (dbColumn.converter() != null && !dbColumn.converter().isInterface()) {
ConverterUtilsWrapper.register(dbColumn.converter().newInstance(), field.getType());
}
try {// NOSONAR
if (PropertyUtils.isWriteable(targetDto, field.getName())) {
if (dbColumn.index() >= 0 && dbColumn.index() < row.length) {
// BUG#1865
int columnIndex = field.getAnnotation(DbColumn.class).index(); PropertyUtils.setProperty(targetDto, field.getName(),
ConverterUtilsWrapper.convert(row[columnIndex], field.getType()));
} else {
throw new SystemException("类" + targetClass.getName() + "的字段" + field.getName()
+ "没有正确设置字段索引,应设置小于" + row.length + "的索引值", new Exception("类"
+ targetClass.getName() + "的字段" + field.getName() + "没有正确设置字段索引,应设置小于"
+ row.length + "的索引值"), SystemException.REQUEST_EXCEPTION);
}
} } catch (InvocationTargetException e) {
LOGGER.error("[Error!]", e);
} catch (NoSuchMethodException e) {
LOGGER.error("[Error!]", e);
}
}
resultList.add(targetDto);
} catch (InstantiationException e1) {
LOGGER.error("[Error!]", e1);
} catch (IllegalAccessException e1) {
LOGGER.error("[Error!]", e1);
} }
return resultList;
} else {
List<Field> annotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbNameColumn.class,
targetClass);
for (Object obj : queryResult) {
try {// NOSONAR
D targetDto = targetClass.newInstance();
Map<String, ?> row = (Map<String, ?>) obj;
for (Field field : annotationFieldList) {
DbNameColumn dbColumn = field.getAnnotation(DbNameColumn.class);
if (dbColumn.converter() != null && !dbColumn.converter().isInterface()) {
ConverterUtilsWrapper.register(dbColumn.converter().newInstance(), field.getType());
}
try {// NOSONAR
if (PropertyUtils.isWriteable(targetDto, field.getName())) {
String colName = dbColumn.fieldName();
PropertyUtils.setProperty(targetDto, field.getName(),
ConverterUtilsWrapper.convert(row.get(colName.toUpperCase()), field.getType()));
}
} catch (InvocationTargetException e) {
LOGGER.error("[Error!]", e);
} catch (NoSuchMethodException e) {
LOGGER.error("[Error!]", e);
}
}
resultList.add(targetDto);
} catch (InstantiationException e1) {
LOGGER.error("[Error!]", e1);
} catch (IllegalAccessException e1) {
LOGGER.error("[Error!]", e1);
}
}
return resultList;
}
} }
以上是完整的映射规则的实现.类很长,但是核心代码不多..总共就2条分支,一条是index来映射,一条是column name来映射,分别对应前面的2种规则.
index那种映射真的不好.所以我就分享下DbNameColumn那套映射规则.核心代码如下:
List<Field> annotationFieldList = AnnotationUtils.getAnnotationFieldsInClass(DbNameColumn.class,
targetClass);
for (Object obj : queryResult) {
try {// NOSONAR
D targetDto = targetClass.newInstance();
Map<String, ?> row = (Map<String, ?>) obj;
for (Field field : annotationFieldList) {
DbNameColumn dbColumn = field.getAnnotation(DbNameColumn.class);
if (dbColumn.converter() != null && !dbColumn.converter().isInterface()) {
ConverterUtilsWrapper.register(dbColumn.converter().newInstance(), field.getType());
}
try {// NOSONAR
if (PropertyUtils.isWriteable(targetDto, field.getName())) {
String colName = dbColumn.fieldName();
PropertyUtils.setProperty(targetDto, field.getName(),
ConverterUtilsWrapper.convert(row.get(colName.toUpperCase()), field.getType()));
}
} catch (InvocationTargetException e) {
LOGGER.error("[Error!]", e);
} catch (NoSuchMethodException e) {
LOGGER.error("[Error!]", e);
}
}
resultList.add(targetDto);
} catch (InstantiationException e1) {
LOGGER.error("[Error!]", e1);
} catch (IllegalAccessException e1) {
LOGGER.error("[Error!]", e1);
}
}
实现细节:
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里就可以了..
不过不管怎么说...这只是实现细节的不同.....现在的实现也是一种不错的实现...
分享公司DAO层数据库结果映射到对象的方法的更多相关文章
- 分享公司DAO层动态SQL的一些封装
主题 公司在DAO层使用的框架是Spring Data JPA,这个框架很好用,基本不需要自己写SQL或者HQL就能完成大部分事情,但是偶尔有一些复杂的查询还是需要自己手写原生的Native SQL或 ...
- DAO层,Service层,Controller层、View层 的分工合作
DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口 ...
- [转]DAO层,Service层,Controller层、View层
来自:http://jonsion.javaeye.com/blog/592335 DAO层 DAO 层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DA ...
- DAO层,Service层,Controller层、View层
DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口 ...
- 分层 DAO层,Service层,Controller层、View层
前部分摘录自:http://blog.csdn.net/zdwzzu2006/article/details/6053006 DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务 ...
- DAO层,Service层,Controller层、View层介绍
来自:http://jonsion.javaeye.com/blog/592335 DAO层 DAO 层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DA ...
- Java中Action层、Service层、Modle层和Dao层的功能区分
一.Java中Action层.Service层.Modle层和Dao层的功能区分: 首先,这是现在最基本的分层方式,结合了SSH架构. modle层就是对应的数据库表的实体类.(即domain) Da ...
- SSH框架中POJO层, Dao层,Service层, Action层的功能理解
pojo层就是对应的数据库表的实体类(如User类). dao层,一般可以再分为***dao接口和***daoImpl实现类,如userDao接口和userDaoImpl实现类,接口负责定义数据库cu ...
- DAO层,Service层,Controller层、View层协同工作机制
转自 http://www.blogdaren.com/post-2024.html DAO层:DAO层主要是做数据持久层的工 作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计D ...
随机推荐
- easyui表格的增删改查
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Java 异常处理
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error:如果你用System.out ...
- Linux下ps命令详解 Linux下ps命令的详细使用方法
http://www.jb51.net/LINUXjishu/56578.html Linux下的ps命令比较常用 Linux下ps命令详解Linux上进程有5种状态:1. 运行(正在运行或在运行队列 ...
- crontab 启动 、运行 和编辑 查看
cron服务是Linux的内置服务,但它不会开机自动启动.可以用以下命令启动和停止服务: /sbin/service crond start /sbin/service crond stop /sbi ...
- 腾讯云CentOS系统配置apache和tomcat
本文使用yum软件包管理工具基于CentOS7.2版本配置apache和tom. 云服务器选购完毕后,安装Xshell软件,输入用户名密码即可远程登陆登录(centos用户名默认是root). 1,下 ...
- Geeklink引领智慧新生活!
煤油灯成为古董,管道天然气进入厨房,电脑挤进生活,手机代替书信成为通讯的主要工具-这些变化无不提醒我们,时代在变迁,科技在发展.而最近朋友圈和电视又在播报智能家居的生活方式-智能家电能实现怎样的情景功 ...
- angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构
ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入 ...
- Python文件读写
读写文件是最常见的IO操作.Python内置了读写文件的函数,用法和C是兼容的 文件打开模式 模式 意义 r 文本只读 w 文本只写 rb 二进制读 rw 二进制写 打开文件 选择一个模式打开一个文件 ...
- 冰冻三尺非一日之寒--js dom
1. 写页面是觉得丑 float,clear:both,margin,padding position: left: 网 ...
- mongodb安装启动遇到的问题
好不容易下载到了mongodb,配置的时候遇到了不少问题. 下载的是解压包,不是官网的,有一个bin目录,解压到一个自己想要的目录,如d:\mongo,首先把bin复制进来,然后创建data目录,da ...