分享公司DAO层动态SQL的一些封装
主题
公司在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的一些封装的更多相关文章
- 分享公司DAO层数据库结果映射到对象的方法
主题 前面写过一篇文章,分享了公司是怎么动态封装SQL查询条件的(http://www.cnblogs.com/abcwt112/p/5874401.html). 里面提到数据库查询结果二维数组最后是 ...
- Dao层向sql语句传递多个参数
手动封装: serviceImpl层 Map<String, Object> params = new HashMap<String, Object>(2);params.pu ...
- MyBatis进阶--接口代理方式实现Dao 和动态SQL
MyBatis接口代理方式实现Dao层 接口代理方式-实现规则 传统方式实现Dao层,我们既要写接口.还要写实现类.而MyBatis框架可以帮助我们省略写Dao层接口实现类的步骤.程序员只需要编写接口 ...
- Dao层的sql语句
2018-08-12 21:33:43 反思:在数据库执行的时候,sql语句是正确的,复制到方法中,执行出错 因为把限定条件改为?时,把左括号删掉了,sql语句报错 改正:一定要确保sql ...
- 动态sql构建的过程
基本原理:使用xsqlbuilder框架完成动态sql的构建. 基本流程:使用WebUtils.getParametersStartingWith(ServletActionContext.getRe ...
- 关于mysql,需要掌握的基础(二):JDBC和DAO层
目录 关于mysql,需要掌握的基础(二):JDBC和DAO层 1.了解jdbc是什么? 2.加载注册驱动:为什么Class.forName("com.mysql.jdbc.Driver ...
- 最常用的动态sql语句梳理——分享给使用Mybatis的小伙伴们!
公司项目中一直使用Mybatis作为持久层框架,自然,动态sql写得也比较多了,最常见的莫过于在查询语句中使用if标签来动态地改变过滤条件了.Mybatis的强大特性之一便是它的动态sql,免除了拼接 ...
- Mybatis进阶学习笔记——动态代理方式开发Dao接口、Dao层(推荐第二种)
1.原始方法开发Dao Dao接口 package cn.sm1234.dao; import java.util.List; import cn.sm1234.domain.Customer; pu ...
- MyBatis开发Dao层的两种方式(Mapper动态代理方式)
MyBatis开发原始Dao层请阅读我的上一篇博客:MyBatis开发Dao层的两种方式(原始Dao层开发) 接上一篇博客继续介绍MyBatis开发Dao层的第二种方式:Mapper动态代理方式 Ma ...
随机推荐
- 使用bulkload向hbase中批量写入数据
1.数据样式 写入之前,需要整理以下数据的格式,之后将数据保存到hdfs中,本例使用的样式如下(用tab分开): row1 N row2 M row3 B row4 V row5 N row6 M r ...
- 安装linxu6.4
RHEL6.3系统安装 进入安装界面 这里选择跳过 点击下一步 选择安装语言 选择键盘 选择系统储存方式 选择是否格式化储存设备 给安装的系统一个计算机名 选择时区 给root一个密码 可以忽略或给一 ...
- 初识Android Studio
刚开始接触Android Studio,很多不适应的地方,自己慢慢摸索,记录下了一些问题和解决途径. 为了能使用android虚拟机,需要下载镜像,镜像有基于arm架构的也有基于intelx86.x6 ...
- vim 学习积累(一)
首先是简单的认识了三种状态(大家公认的说法是模式),分别是:插入,视图,和一般. 进入vim之后默认的是一般模式,这时直接使用'a', 'i', 'o'(也就是进入vim之后直接按下a/i/o键均可进 ...
- 三维网格精简算法(Quadric Error Metrics)附源码
在计算机图形应用中,为了尽可能真实呈现虚拟物体,往往需要高精度的三维模型.然而,模型的复杂性直接关系到它的计算成本,因此高精度的模型在几何运算时并不是必须的,取而代之的是一个相对简化的三维模型,那么如 ...
- jQuery.ajaxComplete() 函数详解
ajaxComplete()函数用于设置当AJAX请求完成(无论成功或失败)时执行的回调函数. 这是一个全局AJAX事件函数,用于为所有AJAX请求的ajaxComplete事件绑定事件处理函数.当A ...
- oracle add_months函数
oracle add_months函数 add_months 函数主要是对日期函数进行操作,举例子进行说明 add_months 有两个参数,第一个参数是日期,第二个参数是对日期进行加减的数字(以月为 ...
- python3条件控制if
Python条件语句是通过一条或多条语句的执行结果(为真或假)来决定执行哪部分代码. if语句 if语句的一般形式如下: if 条件1: 语句1 elif 条件2: 语句2 else: 语句3 其意思 ...
- [LeetCode] Encode and Decode Strings 加码解码字符串
Design an algorithm to encode a list of strings to a string. The encoded string is then sent over th ...
- Zend Framework 项目 index.php 的问题
默认生成的Zend项目在public目录下会自动生成一个.htaccess文件,这是用来实现伪静态,即隐藏index.php这个唯一入口文件的. 但是,搭建项目时遇到一个问题:URL中如果不加inde ...