我把之前发布在简书的两篇文章通过拦截器Interceptor优化Mybatis的in查询 和Mybatis中foreach标签内list为空的解决方案进行了整合,整理为本文的内容。此外,我还对代码部分进行了优化,增加了必要的注释。希望大家阅读愉快。

在工作中,我们经常会因为在mybatis中的不严谨写法,导致foreach解析后的sql语句产生in()或values()的情况,而这种情况不符合SQL的语法,最终会导致bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException的问题。

看到这个报错,大家肯定就会意识到是sql语句的问题,那么我们该如何解决这个问题呢?

网络上有一些现成的解决方案:

1、对list判null和判空来处理

<if test="list != null and list.size>0">do something
</if>
复制代码

这种方案解决了sql语句有误的问题,但同时产生了一个新的逻辑问题。本来预想的in一个空列表,查询结果应该是没有数据才对,但实际上这么写会导致这个in条件失效,这就导致了执行结果并非不是我们想要的问题。

2、对list做双重判断。第一重判断和上面的解决方案一致,增加的第二重判断是为了保证如果list为空列表则只能查到空列表

<if test="list != null and list.size>0">do something
</if>
<if test="list!=null and list.size==0">and 1=0
</if>
复制代码

这种方案能解决sql报错的问题,也不会产生逻辑错误的情况。但是这个写法有点繁琐,每次遇到这种情况都需要特殊判断,会极大降低开发的效率。

那么还有更优雅的写法么?

答案肯定是有的,我们可以通过拦截器Interceptor来优雅的解决这个问题。其他业务同学,还是和往常一样,只需要在xml中判断list非null就可以了。

@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class EmptyCollectionIntercept implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {//通过invocation.getArgs()可以得到当前执行方法的参数//第一个args[0]是MappedStatement对象,第二个args[1]是参数对象parameterObject。final Object[] args = invocation.getArgs();MappedStatement mappedStatement = (MappedStatement) args[0];Object parameter = args[1];if (parameter == null) {Class parameterType = mappedStatement.getParameterMap().getType();// 实际执行时的参数值为空,但mapper语句上存在输入参数的异常状况,返回默认值if (parameterType != null) {return getDefaultReturnValue(invocation);}return invocation.proceed();}BoundSql boundSql = mappedStatement.getBoundSql(parameter);if (isHaveEmptyList(boundSql.getSql())) {return getDefaultReturnValue(invocation);}return invocation.proceed();}@Overridepublic Object plugin(Object target) {//只拦截Executor对象,减少目标被代理的次数if (target instanceof Executor) {return Plugin.wrap(target, this);} else {return target;}}@Overridepublic void setProperties(Properties properties) {}/*** 返回默认的值,list类型的返回空list,数值类型的返回0** @param invocation* @return*/private Object getDefaultReturnValue(Invocation invocation) {Class returnType = invocation.getMethod().getReturnType();if (returnType.equals(List.class)) {return Lists.newArrayList();} else if (returnType.equals(Integer.TYPE) || returnType.equals(Long.TYPE)|| returnType.equals(Integer.class) || returnType.equals(Long.class)) {return 0;}return null;}/*** 去除字符中的干扰项,避免字符串中的内容干扰判断。** @param sql* @return*/private static String removeInterference(String sql) {Pattern pattern = Pattern.compile("[\"|'](.*?)[\"|']");Matcher matcher = pattern.matcher(sql);while (matcher.find()) {String replaceWorld = matcher.group();sql = sql.replace(replaceWorld, "''");}return sql;}/*** 判断是否存在空list** @param sql* @param methodName* @return*/private static Boolean isHaveEmptyList(String sql, String methodName) {sql = removeInterference(sql);List<String> keyWorldList = Lists.newArrayList("in", "values");Boolean isHaveEmptyList = Boolean.FALSE;for (String keyWorld : keyWorldList) {List<Integer> indexList = Lists.newArrayList();//获取关键词后的index,关键词前必须为空白字符,但以关键词开头的单词也会被匹配到,例如indexPattern pattern = Pattern.compile("\\s(?i)" + keyWorld);Matcher matcher = pattern.matcher(sql);while (matcher.find()) {indexList.add(matcher.end());}if (CollectionUtils.isNotEmpty(indexList)) {isHaveEmptyList = checkHaveEmptyList(sql, indexList);if (isHaveEmptyList) {break;}}}return isHaveEmptyList;}/*** 判断sql在indexList的每个index后是否存在存在空列表的情况** @param sql* @param indexList keyWorld在sql中的位置* @return*/private static Boolean checkHaveEmptyList(String sql, List<Integer> indexList) {Boolean isHaveEmptyList = Boolean.FALSE;//获取()内的内容Pattern p2 = Pattern.compile("(?<=\\()(.+?)(?=\\))");for (Integer index : indexList) {String subSql = sql.substring(index);//如果关键词之后无任何sql语句,则sql语句结尾为关键词,此时判定为空列表if (StringUtils.isEmpty(subSql)) {isHaveEmptyList = Boolean.TRUE;break;}//关键词后必须是(或者是空字符或者是换行符等才有继续判断的意义,避免sql中存在以关键词in或values开头的单词的情况干扰判断boolean flag = subSql.startsWith("(")|| subSql.startsWith(" ")|| subSql.startsWith("\n")|| subSql.startsWith("\r");if (!flag) {continue;}subSql = subSql.trim();//如果关键词后的sql语句trim后不以(开头,也判定为空列表if (!subSql.startsWith("(")) {isHaveEmptyList = Boolean.TRUE;break;}Matcher m2 = p2.matcher(subSql);//如果括号()内的内容trim后为空,则判定为空列表if (m2.find()) {if (StringUtils.isEmpty(m2.group().trim())) {isHaveEmptyList = Boolean.TRUE;break;}}}return isHaveEmptyList;}
}
复制代码

具体的判断过程如上所示,关键代码已写注释,阅读起来应该不费事。

最后,把我们写的拦截器加入到sqlSessionFactory的plugins即可投入使用。

<property name="plugins"><array><bean class="com.yibao.broker.intercepts.listIntercept.MyBatisCheckEmptyBeforeExecuteInterceptor"><property name="properties"><value>property-key=property-value</value></property></bean></array>
</property>
复制代码

关于mybatis的拦截器Interceptor,有兴趣的可以自行查阅一下。

这个插件在我司已使用了1年多,目前正常运作。如果大家在使用过程中有什么问题,欢迎留言联系。

Mybatis系列:解决foreach标签内list为空的问题相关推荐

  1. Mybatis新版使用foreach标签遍历Set集合

    2019独角兽企业重金招聘Python工程师标准>>> 文章来源:https://my.oschina.net/u/3844121/blog/1976103 网上的答案没有谈遍历Se ...

  2. Mybatis系列全解(八):Mybatis的9大动态SQL标签你知道几个?提前致女神!

    封面:洛小汐 作者:潘潘 2021年,仰望天空,脚踏实地. 这算是春节后首篇 Mybatis 文了~ 跨了个年感觉写了有半个世纪 - 借着女神节 ヾ(◍°∇°◍)ノ゙ 提前祝男神女神们越靓越富越嗨森! ...

  3. MyBatis系列:mybatis用foreach循环添加多条数据!

    MyBatis系列:mybatis用foreach循环添加多条数据! 前言 今天博主将为大家分享MyBatis系列:mybatis用foreach循环添加多条数据!不喜勿喷,如有异议欢迎讨论!欢迎关注 ...

  4. MyBatis foreach 标签常用方法总结

    一.前言   在 MyBatis 中,常常会遇到集合类型的参数,虽然我们可以通过 OGNL 表达式来访问集合的某一个元素,但是 OGNL 表达式无法遍历集合.foreach 标签就是专门用来解决这类问 ...

  5. 【MyBatis框架】mapper配置文件-foreach标签

    foreach标签 下面介绍一下一个mapper配置文件中的foreach标签(注意,要跟着前面的总结来看,这里使用的例子是结合前面的工程写的,大部分代码没有再赘述) foreach的作用是向sql传 ...

  6. 微软BI 之SSRS 系列 - 解决Pie Chart 中控制标签外部显示与标签重叠的问题

    微软BI 之SSRS 系列 - 解决Pie Chart 中控制标签外部显示与标签重叠的问题 参考文章: (1)微软BI 之SSRS 系列 - 解决Pie Chart 中控制标签外部显示与标签重叠的问题 ...

  7. mybatis where、set、trim、sql、foreach标签的使用

    mybatis where标签的使用 where后面跟查询条件 简化sql语句中判断条件的书写 例: <select id="user" parameterType=&quo ...

  8. Mybatis之foreach标签

    Mybatis之foreach标签 案例:通过foreach标签实现如下sql查询,并在测试类中传入参数: select * from mybatis.blog where id in=(1 or 2 ...

  9. MyBatis学习——foreach标签的使用

    一.foreach标签属性解读 MyBatis的foreach标签应用于多参数的交互如:多参数(相同参数)查询.循环插入数据等,foreach标签包含collection.item.open.clos ...

最新文章

  1. SAP PM 初级系列25 - 维修工单与采购单据之间LINK?
  2. 卡尔曼滤波与组合导航原理_卫星知识科普:一种基于卫星共视的卡尔曼滤波算法!...
  3. python调用c++动态库_Python调用C/C++动态链接库的方法
  4. java 获取mp4 缩略图_java获取视频缩略图
  5. 使用撤回流RetractStream的场景
  6. cmakelists语法_CMakeList语法知识
  7. pandas之groupby分组与pivot_table透视
  8. STL源码剖析 deque双端队列 概述
  9. 2-SAT 问题(洛谷-P4782)
  10. eclipse android 慢,Android编译很慢(使用Eclipse)
  11. C语言进制转换以及原补反码位运算介绍
  12. 简单实现将GIF图片转换为字符画
  13. 我的世界基岩版json_我的世界基岩版1.12指令大全 中国版指令大全列表
  14. Ubuntu16.04使用sudo add-apt-repository时报错:aptsources.distro.NoDistroTemplateException
  15. android老人字体变大,适合老年人用的安卓手机软件 一键让Android字体变大
  16. MusicLM:Generating Music From Text
  17. mysql 小球_c语言编程实例——小球跳动
  18. 用range函数解码高斯等差数列求和
  19. Python基础篇4:判断用户输入的数是正数还是负数
  20. gitlab集成ladp部分用户登录403

热门文章

  1. Pycharm跳转回之前所在的代码行
  2. 《AutoCAD 2016中文版室内装潢设计从入门到精通》——第2章 AutoCAD 2016入门2.1 操作界面...
  3. AlwaysOn 部分笔记及文档连接
  4. spring 中读取properties 文件
  5. CentOS6.4之文本编辑器Vi/Vim
  6. android之利用SQLite数据库实现登陆和注册
  7. javascript定时器
  8. curl比较有用的参数
  9. ClassLoader(二)- 加载过程
  10. AngularJS转换请求内容