Mybatis:解决调用带有集合类型形参的mapper方法时,集合参数为空或null的问题

此文章有问题,待修改!

使用Mybatis时,有时需要批量增删改查,这时就要向mapper方法中传入集合类型(List或Set)参数,下面是一个示例。

但是如果传入的集合类型参数为null或空集合会怎样呢?如果集合类型参数为null,程序调用方法时抛出NullPointerException;如果集合类型参数为空集合,渲染出来的sql语句将会是”select * from user where id in ;”,执行sql时也会报错。

这类问题经典的解决办法有两种。第一种方法,在调用mapper方法前,检查方法实参是否为null或空集合;第二种方法:在XXMapper.xml的CRUD元素中使用

上面的两种方法都需要在许多地方增加检查代码,显得不够优雅,有没有比较优雅的方法呢?有,使用Mybatis拦截器。拦截器可以拦截mapper方法的执行,根据条件决定mapper方法如何执行,如果传入的参数为空集合,则返回默认值(空集合、0或null)。下面是一个示例。

1 package demo.persistence.mybatis.interceptor;
  2
  3 import org.apache.ibatis.cache.CacheKey;
  4 import org.apache.ibatis.executor.Executor;
  5 import org.apache.ibatis.mapping.BoundSql;
  6 import org.apache.ibatis.mapping.MappedStatement;
  7 import org.apache.ibatis.plugin.Interceptor;
  8 import org.apache.ibatis.plugin.Intercepts;
  9 import org.apache.ibatis.plugin.Invocation;
 10 import org.apache.ibatis.plugin.Signature;
 11 import org.apache.ibatis.session.ResultHandler;
 12 import org.apache.ibatis.session.RowBounds;
 13 import org.jetbrains.annotations.NotNull;
 14 import org.jetbrains.annotations.Nullable;
 15
 16 import java.lang.reflect.Method;
 17 import java.lang.reflect.Parameter;
 18 import java.util.*;
 19 import java.util.concurrent.ConcurrentHashMap;
 20 import java.util.concurrent.ConcurrentSkipListSet;
 21
 22 import static org.springframework.util.StringUtils.quote;
 23 import static demo.consts.IntegerType.isIntegerType;
 24 import static demo.consts.RegularExpression.CLASS_METHOD_DELIMITER;
 25
 26 /**
 27  * 此Mybatis拦截器处理mapper方法中集合类型参数为null或为空的情况。如果集合参数为null或为空,则mapper方法的返回值
 28  * 为空集合、0或null,具体返回值视方法本身的返回值而定。
 29  * 注意:① 有的mapper方法将其所需参数放入Map中,此拦截器不处理此类情况;
 30  * ② 有时,向mapper方法传递null参数被视为错误,但此拦截器将其当做正常情况处理
 31  */
 32 // Interceptors注解中写要拦截的的方法签名,但是此处要拦截的方法不是mapper类中的方法,而是Executor类中的方法。
 33 // 可能Mybatis在执行mapper方法时是通过Executor类中的方法来执行的吧。
 34 @Intercepts({
 35         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
 36                 RowBounds.class, ResultHandler.class}),
 37         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
 38                 RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
 39         @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
 40 public class EmptyCollectionArgsInterceptor implements Interceptor {
 41
 42     // 缓存具有集合参数的mapper方法名字以及集合参数的名字,执行这些方法时需要检查它的方法参数是否为null或为空
 43     private final static Map> REQUIRE_CHECK = new ConcurrentHashMap<>();
 44     // 缓存没有集合参数的mapper方法名字,执行这些方法时不需要检查它的方法参数
 45     private final static Set NOT_CHECK = new ConcurrentSkipListSet<>();
 46
 47     @Override
 48     public Object intercept(@NotNull Invocation invocation) throws Throwable {
 49         // 获得Executor方法的实参数组,第一个参数是MappedStatement对象,第二个参数是mapper方法的参数
 50         final Object[] executorMethodArgs = invocation.getArgs();
 51         MappedStatement mappedStatement = (MappedStatement) executorMethodArgs[0];
 52         // 关于mapperMethodArgs变量的说明:
 53         // (1) 如果mapper方法只有一个参数
 54         // ① 如果该参数实际为null,则mapperMethodArgs值为null;
 55         // ② 如果该参数为Map类型且不为null,则mapperMethodArgs的值就是该Map参数的值
 56         // ③ 如果该参数为List类型且不为null,则mapperMethodArgs的类型为MapperMethod.ParamMap(继承于HashMap),
 57         //    Map中有三对键值,它们的值都是该List类型实参,键则分别为"collection"、"list"和List形参的名字
 58         // ④ 如果该参数为Set类型且不为null,则mapperMethodArgs的类型为MapperMethod.ParamMap(继承于HashMap),
 59         //    Map中有两对键值对,它们的值都是该List类型实参,键则分别为"collection"和Set形参的名字
 60         // (2) 如果mapper方法有多个参数,无论实参是否为null,mapperMethodArgs的类型始终为MapperMethod.ParamMap,
 61         //     Map中的键值对就是mapper方法的形参名字与实参值的对,此时集合类型参数没有别名
 62         Object mapperMethodArgs = executorMethodArgs[1];
 63         // mapper方法id,就是在XXMapper.xml的CRUD元素中写的id,而且在该id前加上了对应mapper接口的全限定类名
 64         final String mapperMethodId = mappedStatement.getId();
 65
 66         // 通过mapperMethodId判断该mapper方法是否有集合参数。如果mapperMethodId尚未缓存,requireCheck()方法会将其缓存。
 67         if (requireCheck(mapperMethodId)) {
 68             // 如果该mapper方法有集合参数
 69             // 而mapperMethodArgs为null,显然传入该mapper方法的实参为null,这时应该返回默认值
 70             if (mapperMethodArgs == null) {
 71                 return getDefaultReturnValue(invocation);
 72             }
 73             // 如果mapperMethodArgs不为null,那么它一定是Map类型的参数
 74             Map argMap = (Map) mapperMethodArgs;
 75             final Set requiredNotEmptyArgs = REQUIRE_CHECK.get(mapperMethodId);
 76             for (String requiredNotEmptyArg : requiredNotEmptyArgs) {
 77                 // 从argMap取出所有集合类型的实参,检查它是否为null或是否为空。如果是,则返回默认值
 78                 final Object arg = argMap.get(requiredNotEmptyArg);
 79                 if (arg == null || ((Collection) arg).isEmpty()) {
 80                     return getDefaultReturnValue(invocation);
 81                 }
 82             }
 83         }
 84
 85         // 如果上述检查没有问题,则让mapper方法正常执行
 86         return invocation.proceed();
 87     }
 88
 89     /**
 90      * 当mapper方法出错时返回的默认值。
 91      * @return 如果Executor方法返回List类型对象,则此方法返回空List;如果Executor方法返回数字,则此方法返回0;其余情况返回null。
 92      */
 93     private @Nullable Object getDefaultReturnValue(@NotNull Invocation invocation) {
 94         Class returnType = invocation.getMethod().getReturnType();
 95         if (returnType.equals(List.class)) {
 96             return Collections.emptyList();
 97             // isIntegerType()方法判断Class对象是不是整数Class,自己写
 98         } else if (isIntegerType(returnType)) {
 99             return 0;
100         }
101         return null;
102     }
103
104     /**
105      * 检查mapper方法是否有集合类型参数。
106      * 注意:此方法有副作用。
107      * @param mapperMethodId mapper方法。由mapper类的全限定名和方法名字组成。可由MappedStatement.getId()方法获取。
108      * @throws ClassNotFoundException 如果未能找到指定的mapper方法的类
109      * @throws NoSuchMethodException 如果未能找到指定的mapper方法
110      */
111     private static boolean requireCheck(String mapperMethodId) throws ClassNotFoundException, NoSuchMethodException {
112         // 如果该方法名字存在于无需检查方法集合中,说明该方法无需检查,返回false
113         if (NOT_CHECK.contains(mapperMethodId)) {
114             return false;
115         }
116         // 如果该方法名字存在于需要检查方法Map中,说明该方法需要检查,返回true
117         if (REQUIRE_CHECK.containsKey(mapperMethodId)) {
118             return true;
119         }
120
121         // 如果方法名字不在缓存中,则进行以下操作:
122         // 从完整方法名中分割出全限定类名和方法名
123         // CLASS_METHOD_DELIMITER是类和方法分隔符,自己写吧
124         final String[] fullClassAndMethod = mapperMethodId.split(CLASS_METHOD_DELIMITER, 2);
125         final String fullQualifiedName = fullClassAndMethod[0];
126         final String methodName = fullClassAndMethod[1];
127         Method targetMethod = null;
128         int paramCount = -1;
129         // 遍历指定对应类的全部方法,以找到目标方法
130         for (Method method : Class.forName(fullQualifiedName).getMethods()) {
131             // 个人习惯是在mapper接口中定义几个重载的默认方法,这些默认方法的参数数量比同名的非默认方法的参数数量少,
132             // 所以参数数量最多的方法就是要拦截并检查的方法
133             if (method.getName().equals(methodName) && method.getParameterCount() > paramCount) {
134                 targetMethod = method;
135                 paramCount = method.getParameterCount();
136             }
137         }
138
139         if (targetMethod == null) {
140             throw new NoSuchMethodException("Can't find method " + quote(mapperMethodId));
141         }
142         // 检查目标方法是否有集合参数。如果有,则将该集合参数的名字放入collectionArgNames中。
143         Set collectionArgNames = new HashSet<>();
144         for (Parameter parameter : targetMethod.getParameters()) {
145             if (Collection.class.isAssignableFrom(parameter.getType())) {
146                 collectionArgNames.add(parameter.getName());
147             }
148         }
149         if (collectionArgNames.isEmpty()) {
150             // 如果collectionArgNames为空,说明该方法没有集合参数,不需要检查,返回false
151             // 同时将该方法名字存入无需检查方法集合中
152             NOT_CHECK.add(mapperMethodId);
153             return false;
154         } else {
155             // 如果该collectionArgNames不为空,说明该方法有集合参数,需要检查,返回true
156             // 同时将该方法名字存入需要检查方法Map中
157             REQUIRE_CHECK.put(mapperMethodId, collectionArgNames);
158             return true;
159         }
160     }
161
162 }

要使该拦截器生效,需要在mybatis-config.xml中配置该拦截器,在mybatis-config.xml中添加如下内容即可:

Original: https://www.cnblogs.com/cnblog-user/p/15364283.html
Author: Halloworlds
Title: Mybatis:解决调用带有集合类型形参的mapper方法时,集合参数为空或null的问题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/712252/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • What is a database transaction?

    Atomic (if the change is committed, it happens in one fell swoop; you can never see &#8220…

    技术杂谈 2023年5月31日
    094
  • 响应式编程的实践

    响应式编程在前端开发以及Android开发中有颇多运用,然而它的非阻塞异步编程模型以及对消息流的处理模式也在后端得到越来越多的应用。除了Netflix的OSS中大量使用了响应式编程…

    技术杂谈 2023年5月31日
    094
  • 多态

    总结: 多态是一种代码现象 也是一种思想 定义:一个对象,在不同时刻表现出来的多种形态 前提: 存在继承/实现关系 子类重写父类方法/实现类重写接口方法 父类引用指向子类对象/接口…

    技术杂谈 2023年6月21日
    087
  • 小熊飞桨练习册-05水果数据集

    文件说明 文件 说明 train.py 训练程序 test.py 测试程序 test-gtk.py 测试程序 GTK 界面 report.py 报表程序 onekey.sh 一键获…

    技术杂谈 2023年7月23日
    0107
  • AtCoder Beginner Contest 235

    AtCoder Beginner Contest 235 A – Rotate 思路分析: 因为他给的数是三位数,我们直接取出每一位,然后拼凑就可以了 代码如下: #i…

    技术杂谈 2023年7月24日
    080
  • CAIL2021-阅读理解任务-数据预处理模块(二)

    代码地址:https://github.com/china-ai-law-challenge/CAIL2021/ /* * @Author: Yue.Fan * @Date: 20…

    技术杂谈 2023年6月1日
    0100
  • 支付功能测试点

    支付金额 1.小于最小值,如:小于0.01 2.大于最大值/金额上限 3.无实际意义金额,如0元 4.格式错误(负数、非数字) 5.余额小于实际需要支付的金额 6.超过第三方支付接…

    技术杂谈 2023年5月31日
    0109
  • 李呈祥:bilibili在湖仓一体查询加速上的实践与探索

    导读: 本文主要介绍哔哩哔哩在数据湖与数据仓库一体架构下,探索查询加速以及索引增强的一些实践。主要内容包括: 什么是湖仓一体架构 哔哩哔哩目前的湖仓一体架构 湖仓一体架构下,数据的…

    技术杂谈 2023年7月25日
    078
  • Jedis案例

    案例: 案例需求: 提供index.html页面,页面中有一个省份 下拉列表 当页面加载完成后 发送ajax请求,加载所有省份 代码实现: ProvinceDao package …

    技术杂谈 2023年6月21日
    0143
  • 从事 大数据方向 相关工作得同学看过来啦~~~

    哈喽,从事 大数据方向 相关工作的同学看过来啦~ 为了方便可爱的粉丝们技术之间的交流与答疑,之前建了一个微信技术群,现在宣传一下:无广告,里面的话题只有技术与生活,群里的小伙伴们都…

    技术杂谈 2023年7月25日
    076
  • Windows下_findnext()异常问题

    _findnext()在调试时会出现异常现象,第一个参数”路径句柄”,返回的类型为intptr_t(long long),要改为long long或者int…

    技术杂谈 2023年7月24日
    088
  • Sonarqube安装(Docker)

    一,拉取相关镜像并运行 拉取sonarqube镜像 docker pull sonarqube:9.1.0-community 在运行之前要提前安装postgres并允许,新建数据…

    技术杂谈 2023年7月10日
    081
  • 《鹿柴》王维

    《鹿柴》王维 空山不见人,但闻人语响。 返景入深林,复照青苔上。 posted @2022-08-01 08:20 郑瀚Andrew 阅读(503 ) 评论() 编辑 Origin…

    技术杂谈 2023年5月31日
    098
  • magent memcached多主多从【转】

    可以参考以下链接,先占坑。 magent memcached多主多从_百度搜索https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp…

    技术杂谈 2023年5月31日
    082
  • 《三十年来寻剑客》灵云志勤禅师

    《三十年来寻剑客》灵云志勤禅师 三十年来寻剑客,几回落叶又抽枝。自从一见桃花后,直至如今更不疑。 Original: https://www.cnblogs.com/LittleH…

    技术杂谈 2023年5月31日
    096
  • Kubernetes Deployment 实操笔记

    Deployment 用于部署无状态的服务。一般不直接管理Pod或者ReplicaSet。 apiVersion: apps/v1 kind: Deployment metadat…

    技术杂谈 2023年7月11日
    088
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球