mybatis中动态SQL之trim详解
一. 背景
之前mybatis中<where>、<update>、<if>、<foreach>标签用的多,知道有<trim>这个标签,但很少去用,也没有去深入理解它,直到最近遇到一个问题。问题是这样的:
一个SQL有三个int查询字段a、b、c,表达式为:a=#{a} AND (b=#{b} OR c=#{c})。其中a是必查的,b和c为非必查的(这里假定传入-1表示该字段不参与查询)。那么该表达式会有以下几种形态:
- a=#{a}
- a=#{a} AND b=#{b}
- a=#{a} AND c=#{c}
- a=#{a} AND (b=#{b} OR c=#{c})
看到这个需求后,觉得逻辑还是挺简单的,但写起mapper的SQL来并不是那么容易(如果你有的话,欢迎下边评论贴出来)。考虑了多层<if>、<choose>等标签,虽然也能实现这个功能,但过于繁琐。有没有一种更简单的实现方式?有!<trim>!!!
请尊重作者劳动成果,转载请标明原文链接:http://www.cnblogs.com/waterystone/p/7070836.html
二. 功能描述与用法
网上关于<trim>的介绍并不多,通过看mybatis的源码,一句话描述trim的功能:子句首尾的删除与添加。它就是一个字符串处理工具,类似于replace(),但它只处理首尾。真的是一个神器,其实mybatis中的<set>和<where>都可以用<trim>来实现,但<trim>的功能更强大,使用起来更灵活!!!
<trim prefix="(" prefixOverrides="OR" suffixOverrides="," suffix=")">子句</trim>
这里的子句会对其进行trim()处理,忽略掉换行、空格等字符,所以本文中的子句都是指trim()处理后的字符串。如果子句为空,那么整个<trim>块不起作用,相当于不存在。本<trim>块的作用就是:去掉子句首的OR和子句尾的逗号,并在子句前后分别加上(和),比如,"orabc,"-->"(abc)"。
- prefixOverrides:子句首的命中词列表,以|分隔,忽略大小写。如果命中(轮询命中词,最多只命中一次),会删除子句首命中的词;没命中就算了。
- prefix:删除子句句首后,在子句最前边加上单个空格+prefix。
- suffixOverrides:子句尾的命中词列表,以|分隔,忽略大小写。如果命中(轮询命中词,最多只命中一次),会删除子句尾命中的词;没命中就算了。
- suffix:删除子句句尾后,在子句最后边加上单个空格+suffix。
有了这个神器,处理前文提起的需求,就可以用<trim>很悠然的处理了。
WHERE a = #{a}
<trim prefix="AND(" prefixOverrides="OR" suffix=")">
<if test="b != -1">
OR b = #{b}
</if>
<if test="c != -1">
OR c = #{c}
</if>
</trim>
<trim>的实现非常简单,如果觉得我描述的还不够清楚,可以看下第三部分的源码。
三.源码(org.apache.ibatis.scripting.xmltags.TrimSqlNode)
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer; import org.apache.ibatis.session.Configuration; /**
* @author Clinton Begin
*/
public class TrimSqlNode implements SqlNode { private SqlNode contents;
private String prefix;//前缀
private String suffix;//后缀
private List<String> prefixesToOverride;//子句首命中词列表
private List<String> suffixesToOverride;//子句尾命中词列表
private Configuration configuration; public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));//解析以|分隔的命中词
} protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
} @Override
public boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
boolean result = contents.apply(filteredDynamicContext);
filteredDynamicContext.applyAll();//解析<trim>
return result;
} //将命中词以|分隔,获取命中词列表
private static List<String> parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List<String> list = new ArrayList<String>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));//命中词统一转为大写。
}
return list;
}
return Collections.emptyList();
} private class FilteredDynamicContext extends DynamicContext {
private DynamicContext delegate;
private boolean prefixApplied;//前缀处理标记,表示是否处理过。
private boolean suffixApplied;//后缀处理标记,表示是否处理过。
private StringBuilder sqlBuffer; public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
this.delegate = delegate;
this.prefixApplied = false;
this.suffixApplied = false;
this.sqlBuffer = new StringBuilder();
} public void applyAll() {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());//子句先trim()
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);//跟命中词一致,子句也统一转为大写,所以<trim>对子句的处理是忽略大小写的。
if (trimmedUppercaseSql.length() > 0) {//如果子句非空才处理
applyPrefix(sqlBuffer, trimmedUppercaseSql);//处理前缀
applySuffix(sqlBuffer, trimmedUppercaseSql);//处理后缀
}
delegate.appendSql(sqlBuffer.toString());
} @Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
} @Override
public void bind(String name, Object value) {
delegate.bind(name, value);
} @Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
} @Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
} @Override
public String getSql() {
return delegate.getSql();
} private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!prefixApplied) {//只处理一次
prefixApplied = true;
if (prefixesToOverride != null) {//如果命中词列表非空
for (String toRemove : prefixesToOverride) {//轮询命中词列表
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());//如果句首命中,则删除该句首的命中词
break;//最多删除一次,不会把所有命中词都匹配一遍
}
}
}
if (prefix != null) {//加上前缀。删除原句首,再添上新句首,相当于句首的替换。
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
} private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if (!suffixApplied) {//只处理一次
suffixApplied = true;
if (suffixesToOverride != null) {//如果命中词列表非空
for (String toRemove : suffixesToOverride) {//轮询命中词列表
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);//如果句尾命中,则删除该句尾的命中词
break;//最多删除一次,不会把所有命中词都匹配一遍
}
}
}
if (suffix != null) {//加上后缀。删除原句尾,再添上新句尾,相当于句尾的替换。
sql.append(" ");
sql.append(suffix);
}
}
} } }
mybatis中动态SQL之trim详解的更多相关文章
- MyBatis中动态SQL元素的使用
掌握MyBatis中动态SQL元素的使用 if choose(when,otherwise) trim where set foreach <SQL>和<include> 在应 ...
- Mybatis中动态SQL多条件查询
Mybatis中动态SQL多条件查询 mybatis中用于实现动态SQL的元素有: if:用if实现条件的选择,用于定义where的字句的条件. choose(when otherwise)相当于Ja ...
- Mybatis中动态SQL语句中的parameterType不同数据类型的用法
Mybatis中动态SQL语句中的parameterType不同数据类型的用法1. 简单数据类型, 此时#{id,jdbcType=INTEGER}中id可以取任意名字如#{a,jdbcType ...
- MyBatis中动态SQL语句完成多条件查询
一看这标题,我都感觉到是mybatis在动态SQL语句中的多条件查询是多么的强大,不仅让我们用SQL语句完成了对数据库的操作:还通过一些条件选择语句让我们SQL的多条件.动态查询更加容易.简洁.直观. ...
- MyBatis中传入参数parameterType类型详解
前言 Mybatis的Mapper文件中的select.insert.update.delete元素中有一个parameterType属性,用于对应的mapper接口方法接受的参数类型.本文主要给大家 ...
- Mybatis mapper动态代理的原理详解
在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出my ...
- 阶段3 1.Mybatis_08.动态SQL_03.mybatis中动态sql语句-foreach和sql标签
foreach标签 in的查询 sql语句好写,但是传参在映射文件里面改怎么传呢 定义一个List<Integer>成员变量,然后生成get和set 定义一个新的查询方法 open:开始符 ...
- 阶段3 1.Mybatis_08.动态SQL_02.mybatis中动态sql语句-where标签的使用
这里的userSex是实体类里面的属性名,而不是数据库内的字段名称 一个老王改成性别女,为了区分一下 增加sex字段的查询 where标签 用上where和刚才的执行效果是一样的 where标签使我们 ...
- Mybatis的动态SQL实现
一.动态SQL简介 MyBatis的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦.拼接的时候要确保不能忘了必要的 ...
随机推荐
- boost asio 学习(五) 错误处理
http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio?pg=6 5. Erro ...
- 学习Acegi应用到实际项目中(1)
在此,本人声明,我处于菜鸟阶段,文章的内容大部分摘自zhanjia的博客(http://zhanjia.iteye.com/category/43399),旨在学习,有很多地方,我理解不够透彻,可能存 ...
- log4j控制台乱码解决办法
1.控制台编码问题 这两天碰到一个问题,在windows启动tomcat的时候tomcat控制台日志突然乱码了, 我发现在log4j.properties文件中加了一行配置log4j.appender ...
- python黑帽子-黑客与渗透测试编程之道(源代码)
链接: https://pan.baidu.com/s/1i5BnB5V 密码: ak9t
- Latex小技巧
最近由于离散数学课程作业的需要, 使用$\LaTeX$写pdf报告, 积累了一些$\LaTeX$小技巧, 以后还会陆续补充. 1. 查看某个包的使用手册, 在命令行中输入texdoc XXX, 其中X ...
- Spring Boot Actuator 使用
转载于:https://www.jianshu.com/p/af9738634a21 Spring Boot 的 Actuator 提供了很多生产级的特性,比如监控和度量Spring Boot 应用程 ...
- JDBC几种常见的数据库连接
JDBC的URL=协议名+子协议名+数据源名.a 协议名总是“jdbc”.b 子协议名由JDBC驱动程序的编写者决定.c 数据源名也可能包含用户与口令等信息:这些信息也可单独提供.几种常见的数据库连接 ...
- 11g统计信息自动收集任务
1.查看统计信息自动收集任务的开启情况. select client_name,status from dba_autotask_client; 2.查看自动收集任务各个窗口的开启情况. col op ...
- hdu 4022 Bombing
Bombing Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Others)Total Sub ...
- org.apache.commons.dbcp.SQLNestedException: Cannot load JDBC driver class 'com.microsoft.sqlserver.jdbc.SQLServerDriver '
问题摘要: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nest ...