主题

  公司在DAO层使用的框架是Spring Data JPA,这个框架很好用,基本不需要自己写SQL或者HQL就能完成大部分事情,但是偶尔有一些复杂的查询还是需要自己手写原生的Native SQL或者HQL.同时公司前端界面使用的是jquery miniui框架,并且自己进行了一些封装.

  当界面上查询条件比较多的时候,需要动态拼接查询条件,使用JPA的话可以通过CriteriaQuery进行面向对象的方式进行查询,但是偶尔有时候又要用到HQL或者SQL,毕竟比CriteriaQuery简单很多.而两者似乎不能很好的结合(我还没见过将两者混用的,不过可能是我CriteriaQuery使用的比较少的原因).

  这也是我写这篇文章的原因,记录分享一下公司的解决办法(公司的策略也不是完美的,只能适用于简单的查询,复杂的情况还是不支持的,但是也不失为一种解决办法)

原理

  公司动态查询的原理是使用HQL或者SQL,在这个基本select语句上动态拼接where条件...公司认为用户在界面上动态选择条件的时候,select 的表基本是不会变化的,有变化的是where字句里的条件.所以需要对用户的输入和where子句里的条件进行封装.

  DefaultPage:这个类是公司对前端界面用户输入的查询条件,比如分页信息等等进行的封装.(当然还有其他很多类..只是这个是最主要的,怎么封装的不是这篇文章的主题)

  SearchCriteria:这个是公司对查询条件where子句的封装,它可以通过一些方法转化成where子句里SQL字符串..

  最主要的就是以上2个类,核心思想就是封装用户输入的查询信息到DefaultPage,从DefaultPage中取得SearchCriteria,将SearchCriteria转化成字符串形式,拼接在基础的SQL(HQL)之上,形成动态的SQL来查询数据.

主要实现

SearchCriteria

SearchCriteria是对where子句的封装,SearchCriteria中包含很多个小的SubCriteria,SubCriteria是对where子句里每个条件的封装.

比如有个where子句是:

where 1=1 and user.username = 'user1' and user.state in ('','');

那整个where语句是1个SearchCriteria

and user.username = 'user1'是第一个SubCriteria

and user.state in ('0','1')是第二个SubCriteria

1=1是为了拼接方便,就算没有查询条件也拼接where条件而增加的一个拼接默认条件

     public SubCriteria(String attName, EOperator operator, Object value, ERelation relation, String tabelAlias) {
this.attName = attName;
this.operator = operator;
this.attValue = value;
this.relation = relation;
this.tableAliasName = tabelAlias;
}

从SubCriteria的构造方法中可以看出,如果一个SubCriteria对应的是and user.username = 'user1'

那attName就是username

operator就是=

value就是'user1'

relation就是and

tableAlias就是user

1个SearchCriteria中肯定会含有N个SubCriteria

List<SubCriteria> list = new ArrayList<SubCriteria>();

查询的数据库结果的方法如下:

     @Override
public <D> List<D> executeDynamicQuerySql(String sql, SearchCriteria criteria, Class<? extends D> targetClass,
Map<String, Object> customParams) {
Map<String, Object> paramMap = new HashMap<String, Object>();
String handledSql = calculateDynamicSql(sql, criteria, paramMap);
mergeCustomParams(paramMap, customParams);
return NativeSqlExecutor.executeQuerySql(getEntityManager(), handledSql, paramMap, targetClass);
}

sql的格式就是类似于 select * from user user %Where_Clause% and user.yxbz = :yxbz     (yxbz是有效标志的意思)

criteria就是界面上动态选择的查询条件和值的封装

targetClass不重要,只是为了把结果封装成对象时候,指定要封装到哪个类型的对象里.(数据库返回结果封装到对象也是公司自己封装的代码)

customParams 里存是sql里一些占位符参数的键值对,比如key=yxbz,value='Y'

第5行代码calculateDynamicSql将Criteria转化成的SQL拼接到传入的sql中,替换掉%Where_Clause%,在把Criteria中的占位符键值对放到paramMap中

第6行,将paramMap与传入的customParams合并,得到一个合并的map,即是把customParams的键值对放到paramMap中.

第7行就是常规的调用JPA的方法,把生成的动态的SQL与参数Map传给JPA去执行,根据targetClass将返回的结果封装成对象.

calcilateDynamicSql具体步骤如下:

    private String calculateDynamicSql(String sql, SearchCriteria criteria, Map<String, Object> paramMap) {
String searchSql = calculateSearchDynamicSql(sql, criteria, paramMap);
return calculateOrderDynamicSql(searchSql, criteria);
} private String calculateSearchDynamicSql(String sql, SearchCriteria criteria, Map<String, Object> paramMap) {
StringBuilder whereClause = new StringBuilder(" WHERE 1=1 ");
int index = paramMap.keySet().size() + 1;
for (SubCriteria subCriteria : criteria.getCreteriaList()) {
whereClause.append(subCriteria.getRelation().getCode());
whereClause.append(StringUtils.isEmpty(subCriteria.getTableAliasName()) ? "" : subCriteria
.getTableAliasName() + ".");
whereClause.append(subCriteria.getAttName());
whereClause.append(" ");
whereClause.append(subCriteria.getOperator().getCode());
whereClause.append(" ");
if (EOperator.IN == subCriteria.getOperator()) { @SuppressWarnings("unchecked")
List<Object> paramValues = (List<Object>) subCriteria.getAttValue();
whereClause.append("(");
for (int i = 0; i < paramValues.size(); i++) {
whereClause.append(PLACEHOLDER);
String key = PARAM_PREFIX + (index++);
whereClause.append(key);
if (i != paramValues.size() - 1) {
whereClause.append(",");
}
paramMap.put(key, paramValues.get(i));
}
whereClause.append(")");
} else {
whereClause.append(PLACEHOLDER);
String key = PARAM_PREFIX + (index++);
whereClause.append(key);
whereClause.append(" ");
paramMap.put(key, subCriteria.getAttValue());
}
}
return sql.replace("%WHERE_CLAUSE%", whereClause.toString());
}

calculateDynamicSql中又分为2个步骤,先根据SearchCriteria计算出where字符串,再根据SearchCriteria计算order by子句...order by子句比where子句简单很多,原理也差不多...就不介绍了..主要看calculateSearchDynamicSql这个计算where子句的方法步骤主要是:

1.先拼接where 1=1 这是为了简化问题,防止用户什么都不选的时候不用拼接where子句的问题,就算动态SQL里什么where条件都不写,也会拼接where 1=1 这个条件来简化问题.

2.有了步骤1可以保证一定拼接了where字符串,后续只要把SubCriteria转化成字符串拼接到where子句中就OK了. 拼接方法如前面介绍SubCriteria所说,就是把SubCriteria中的属性一个一个取出来拼接.唯一有点区别的就是如果SubCriteria中EOperator是in操作符,那传过来的参数值是个list而不是一个String...

mergeCustomParams(paramMap, customParams)方法

拼接完了SQL,就需要把SearchCriteria中的占位符键值对与用户传入的键值对合并.

     private void mergeCustomParams(Map<String, Object> paramMap, Map<String, Object> customParams) {
if (null == customParams || null == paramMap) {
return;
}
for (Map.Entry<String, Object> entry : customParams.entrySet()) {
if (null != entry.getKey()) {
if (paramMap.containsKey(entry.getKey())) {
throw new IllegalArgumentException("动态SQL不允许自定义占位符以 'P_' 开始,该类占位符用于 searchCriteria动态生成。");
}
paramMap.put(entry.getKey(), entry.getValue());
}
}
}

当然其中键值对可能会有同名的...那就报错...

executeQuerySql方法

     public static <D> List<D> executeQuerySql(EntityManager entityManager, String sql, Map<String, Object> paramMap,
Class<? extends D> targetClass) { LOGGER.debug("Execute native query sql {} with parameters {} for target {}", sql, paramMap, targetClass);
Query query = entityManager.createNativeQuery(sql);
if (DbColumnMapper.isNamedMapping(targetClass)) {
query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
}
if (paramMap != null) {
for (Entry<String, Object> entry : paramMap.entrySet()) {
query.setParameter(entry.getKey(), entry.getValue());
}
}
return DbColumnMapper.resultMapping(query.getResultList(), targetClass);
}

核心就是:

1.query.setParameter(entry.getKey(), entry.getValue());根据传入的键值对设置到占位符中

2.query.getResultList()查询结果..其他很多代码是用于把query.getResultList()的结果封装成对象用的..主要方法是在targetClass的类的Field上使用注解标注.

DefaultPage

公司对前后台请求与参数都进行了封装,前面写过一篇文章,介绍了公司的基本思路(当然实现肯定不一样)

http://www.cnblogs.com/abcwt112/p/5169250.html

所以这里不再详细介绍前台用户选择的那些查询条件怎么封装到DefaultPage里了...

我们来看看DefaultPage如何生成SearchCriteria的:

 private SearchCriteria buildSearchCriteria(String buildSearchType) {
SearchCriteria search = new SearchCriteria();
operationMap = this.getOperationMap();
try {
for (Map.Entry<String, Object> entry : parameterMap.entrySet()) {
String paramKey = entry.getKey();
Object value = parameterMap.get(paramKey);
boolean isString = value instanceof String; if (value != null) {
if (isString) {
if (StringUtils.isNotEmpty((String) value)) {
search.add(this.getColumnName(paramKey, buildSearchType), this.parameterMap.get(paramKey),
operationMap.get(paramKey));
this.parameterValues.add(value);
}
} else {
search.add(this.getColumnName(paramKey, buildSearchType), this.parameterMap.get(paramKey),
operationMap.get(paramKey));
this.parameterValues.add(value);
} }
}
for (SearchOrder order : orderBy) {
if (StringUtils.isNotEmpty(order.getOrderName())) {
SearchOrder realOrder = new SearchOrder(this.getColumnName(order.getOrderName(), buildSearchType),
order.isAsc());
search.getOrderByList().add(realOrder);
}
} } catch (Exception ex) {
throw new SystemException(SystemException.REQUEST_EXCEPTION, ex, ex.getMessage());
}
return search;
}

buildSearchType有2种,1种最常用的就是生成我们这里一般的SQL查询的searchCriteria,还有一种适用于SpringDataJpa,用于生成适用于Specification接口的SearchCriteria用的...

生成SearchCriteria主要是为了生成SubCriteria,通过调用SearchCriteria的add方法直接在生成一个SubCriteria并放到SearchCriteria的List<SubCriteria>成员域中.

                             search.add(this.getColumnName(paramKey, buildSearchType), this.parameterMap.get(paramKey),
operationMap.get(paramKey));
     public void add(String attribute, Object value, EOperator operator) {
if (attribute == null || operator == null) {
return;
}
list.add(new SubCriteria(attribute, operator, value));
}

search.add的第一个参数是attribute就是where user.username = :username中的username

getColumn方法如下:

     private String getColumnName(String paramKey, String buildType) throws Exception {// NOSONAR
if (paramKey.indexOf(':') > 0) {
String[] keys = paramKey.split(":");
if (null != keys && keys.length == 2 && cmpClass != null) {
Field field = cmpClass.getDeclaredField(keys[0]);
if (null != field) {
StringBuilder sb = new StringBuilder();
sb.append(keys[0]);
sb.append('.');
if (BUILDTYPE_NATIVE.equalsIgnoreCase(buildType)) {
sb.append(getColumnName(keys[1], buildType, field.getClass()));
} else {
sb.append(keys[1]);
}
return sb.toString();
}
}
}
return getColumnName(paramKey, buildType, cmpClass);
}

大多数情况下是直接调用19行的getColumnName

     private String getColumnName(String paramKey, String buildType, Class<?> clazz) throws Exception {// NOSONAR
String columnName = paramKey;
if (paramKey.endsWith("_start")) {
columnName = paramKey.replaceAll("_start", "");
}
if (paramKey.endsWith("_end")) {
columnName = paramKey.replaceAll("_end", "");
}
if (clazz != null && BUILDTYPE_NATIVE.equalsIgnoreCase(buildType)) {
Field field = clazz.getDeclaredField(columnName);
Column column = field.getAnnotation(Column.class);
if (column != null) {
columnName = column.name();
}
}
return columnName;
}

clazz是前台数据传过来肯定会封装到一个接受对象上,如果那个对象的field上用了注解@Column,那就取注解里写的name作为attribute的name,否则就取前台传过来的参数值作为attribute.

这是因为jpa的注解@column可以让实体类里的字段映射到数据库中表的字段,但是两者的名字可以不同,将SearchCriteria转化成SQL的时候用要使用数据库中的字段名而不是实体类中的属性名.

这样就能构造出一个SearchCriteria了.

以上便是公司对DAO层动态SQL的主要封装逻辑..在查询条件不复杂的情况下还算好用...

但是查询条件比较复杂的话就有点力不从心了..因为SearchCriteria里只有一个SubCriteria的list,而SubCriteria中不能包含SubCriteria...所以像 where (a.b = '1' or a.c = '2') and ....

这样的查询就做不出来...

分享公司DAO层动态SQL的一些封装的更多相关文章

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

    主题 前面写过一篇文章,分享了公司是怎么动态封装SQL查询条件的(http://www.cnblogs.com/abcwt112/p/5874401.html). 里面提到数据库查询结果二维数组最后是 ...

  2. Dao层向sql语句传递多个参数

    手动封装: serviceImpl层 Map<String, Object> params = new HashMap<String, Object>(2);params.pu ...

  3. MyBatis进阶--接口代理方式实现Dao 和动态SQL

    MyBatis接口代理方式实现Dao层 接口代理方式-实现规则 传统方式实现Dao层,我们既要写接口.还要写实现类.而MyBatis框架可以帮助我们省略写Dao层接口实现类的步骤.程序员只需要编写接口 ...

  4. Dao层的sql语句

    2018-08-12     21:33:43 反思:在数据库执行的时候,sql语句是正确的,复制到方法中,执行出错   因为把限定条件改为?时,把左括号删掉了,sql语句报错 改正:一定要确保sql ...

  5. 动态sql构建的过程

    基本原理:使用xsqlbuilder框架完成动态sql的构建. 基本流程:使用WebUtils.getParametersStartingWith(ServletActionContext.getRe ...

  6. 关于mysql,需要掌握的基础(二):JDBC和DAO层

    ​ 目录 关于mysql,需要掌握的基础(二):JDBC和DAO层 1.了解jdbc是什么? 2.加载注册驱动:为什么Class.forName("com.mysql.jdbc.Driver ...

  7. 最常用的动态sql语句梳理——分享给使用Mybatis的小伙伴们!

    公司项目中一直使用Mybatis作为持久层框架,自然,动态sql写得也比较多了,最常见的莫过于在查询语句中使用if标签来动态地改变过滤条件了.Mybatis的强大特性之一便是它的动态sql,免除了拼接 ...

  8. Mybatis进阶学习笔记——动态代理方式开发Dao接口、Dao层(推荐第二种)

    1.原始方法开发Dao Dao接口 package cn.sm1234.dao; import java.util.List; import cn.sm1234.domain.Customer; pu ...

  9. MyBatis开发Dao层的两种方式(Mapper动态代理方式)

    MyBatis开发原始Dao层请阅读我的上一篇博客:MyBatis开发Dao层的两种方式(原始Dao层开发) 接上一篇博客继续介绍MyBatis开发Dao层的第二种方式:Mapper动态代理方式 Ma ...

随机推荐

  1. CSS3:backgroud-size和background-origin的使用方法

    backgroud-size CSS3以前,背景图像大小由图像的实际大小决定,background-size指定背景图像的大小. 你可以指定像素或百分比大小,你指定的大小是相对于父元素的宽度和高度的百 ...

  2. SCNU ACM 2016新生赛初赛 解题报告

    新生初赛题目.解题思路.参考代码一览 1001. 无聊的日常 Problem Description 两位小朋友小A和小B无聊时玩了个游戏,在限定时间内说出一排数字,那边说出的数大就赢,你的工作是帮他 ...

  3. Morris post order traversal algorithm

    Sept. 5, 2015 花时间把代码读明白, 比光看书强. 动手写代码, 改代码,  兴趣是最好的老师. 多记几个例子, 增加情趣. 举个例子关于中序遍历,            4       ...

  4. fixed数据类型

    在处理图形运算,特别是3D图形生成运算时,往往要定义一个Fixed数据类型,我称它为定点数,定点数其时就是一个整形数据类型,他的作用就是把所有数 进行转换,从而得到相应类型的整型表达,然后使用定点数进 ...

  5. codevs 2597 团伙

    时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题目描述 Description 1920年的芝加哥,出现了一群强盗.如果两个强盗遇上了,那么他们要么是朋友,要么 ...

  6. mysql max_allowed_packet 设置过小导致记录写入失败

    mysql根据配置文件会限制server接受的数据包大小. 有时候大的插入和更新会受max_allowed_packet 参数限制,导致写入或者更新失败. 查看目前配置 show VARIABLES ...

  7. Doc

    一:window: 属性(值或者子对象):opener:打开当前窗口的源窗口,如果当前窗口是首次启动浏览器打开的,则opener是null,可以利用这个属性来关闭源窗口. 方法(函数):事件(事先设置 ...

  8. JSP中编译指令include与动作指令include的区别

    include指令是编译阶段的指令,即include所包含的文件的内容是编译的时候插入到JSP文件中,JSP引擎在判断JSP页面未被修改, 否则视为已被修改.由于被包含的文件是在编译时才插入的,因此如 ...

  9. Not Hello World

    通常对于一门语言的学习,一般都是以"Hello,World!"开始的.但对于汇编语言的学习,输出这句话并不容易,首先得了解寄存器等硬件知识. 汇编语言要得以运行,首先要讲源文件编译 ...

  10. Leetcode分类刷题答案&心得

    Array 448.找出数组中所有消失的数 要求:整型数组取值为 1 ≤ a[i] ≤ n,n是数组大小,一些元素重复出现,找出[1,n]中没出现的数,实现时时间复杂度为O(n),并不占额外空间 思路 ...