大家好,我是了不起,前一段时间在工作中因为一个疏忽踩了一个坑,最终通过异常栈追溯源码解决了问题。
下面我来给大家还原一下案发现场,并介绍一下自己的解决思路,希望能对大家有所启发。
当时的业务逻辑主要通过 Mybatis 框架来修改数据,具体示例如下:
import org.springframework.data.repository.query.Param;public interface GroupMapper { int updateGroup(@Param("oldSerial") Set<Integer> oldSerial, @Param("newSerial") int newSerial); int invalidGroup(@Param("set") Set<Integer> set);}
<update id="updateGroup"> update groupCode_table_use set groupCode=#{newSerial} where groupCode in <foreach collection="oldSerial" close=")" open="(" item="item" separator=","> #{item} </foreach></update><update id="invalidGroup"> update groupCode_table set status='无效' where groupCode in <foreach collection="set" close=")" open="(" item="item" separator=","> #{item} </foreach></update>
本以为 so easy 的代码,出现意外了!第一个sql语句updateGroup正常运行,第二个sql语句invalidGroup竟然报错了???
报错信息如下:
图片
从日志上可以看出,提示set参数找不到!
明明使用了@Param("set")将参数命名为set为何找不到,完全不符合多年开发的认知。
更加诡异的是updateGroup已使用了同样的方式去遍历,完全没得问题,那么问题出现在了哪了?
小伙伴可以先猜一猜!
百思不得其解下,我掏出了祖传绝活 debug 源码,最终发现原来是Mybatis对集合Set进行了特殊处理。
案发项目引入的Mybatis版本是 3.5.1。部分源码如下!
org.apache.ibatis.session.defaults.DefaultSqlSession.java@Overridepublic int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); //调用了wrapCollection方法对参数进行了处理 return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}/*** 如果参数实现了Collection接口或是数组类型 wrapCollection方法会对参数进行封装* 如果参数实现了Collection接口会封装为含collection键的Map* 如果参数又实现了List接口会封装为含list键的Map(追增)* 对Set集合没有特殊处理* 如果参数是数组类型会封装为含array键的Map*/private Object wrapCollection(final Object object) { if (object instanceof Collection) { StrictMap<Object> map = new StrictMap<>(); map.put("collection", object); if (object instanceof List) { map.put("list", object); } return map; } else if (object != null && object.getClass().isArray()) { StrictMap<Object> map = new StrictMap<>(); map.put("array", object); return map; } return object;}
通过以上的源码分析,发现Mybatis框架对集合参数进行了特殊处理。
这就是为什么报错信息中提示Available parameters are [collection]。
找到了collection错误信息从哪里来问题,接下来我们分析一下set参数到了哪里去。
首先updateGroup可以正常执行是因为源码中对集合的特殊处理只对单参数生效,也就是说@Param("set")注解失效是因为被Mybatis自家的特殊处理给覆盖了?
这不合乎常理啊,那么问题可能出在@Param注解身上,通过排查代码发现GroupMapper.java类引用的@Param注解不对!
// 代码引入的注解类Paramimport org.springframework.data.repository.query.Param;// 期望的注解类,应该由mybatis提供import org.apache.ibatis.annotations.Param;
实际上,springframework包下的@Param注解执行时机在wrapCollection处理之前,wrapCollection对集合的特殊处理将springframework包下的@Param注解处理覆盖掉了,所以无法解析参数set。而mybatis包下的@Param注解执行时机在wrapCollection处理之后,程序可以正常运行。
最终确定,绕了这么大一圈,原来是包导入错了,谁能想到springframework包下还有一个@Param注解,在多参数的情况下竟然可以正常使用o(╥﹏╥)o。
整个异常排查过程中,主要通过分析异常栈信息进行快速定位。下面我给大家介绍一下异常栈分析法。
我们在工作中查看异常栈,往往都是业务代码异常导致的,这个时候我们只需要关心是在我们编写的哪段代码中出了问题。
而这时好用的idea也会为温馨的为我们做出提示,会在异常栈中将我们编写的方法位置信息标蓝处理,这样我们就定快速定位出现问题的代码。
图片
但是一旦是底层引用的jar包出现了异常,仅仅是这样查看异常栈是不够的。下面我们就以上面案例中的异常栈来带大家分析。
当前异常:
图片
原始异常:
图片
分析流程:
"Caused by" 是 Java 异常处理机制中的一部分,它表示当前异常是由另一个异常引起的。在 Java 中,每个 Throwable 对象都可以通过 getCause() 方法获取到原始异常,这个原始异常就是通过 "Caused by" 打印出来的。
假设你在catch块中捕获了一个异常,并重新抛出了一个新的异常,同时保留了原始异常的信息:
public class Example { public static void main(String[] args) { try { methodThatThrowsException(); } catch (Exception e) { throw new RuntimeException("Caught an exception", e); } } public static void methodThatThrowsException() throws Exception { throw new Exception("Original exception"); }}
当你运行上述代码时,控制台输出的异常栈信息通常会包含Caused by信息:
java.lang.RuntimeException: Caught an exception at Example.main(Example.java:7)Caused by: java.lang.Exception: Original exception at Example.methodThatThrowsException(Example.java:12) at Example.main(Example.java:5)
如何保留原始异常信息呢?
try { methodThatThrowsException(); } catch (Exception e) { throw new RuntimeException("Caught an exception", e); }
try { methodThatThrowsException(); } catch (Exception e) { RuntimeException newException = new RuntimeException("Caught an exception"); newException.addSuppressed(e); throw newException; }
学会查看分析异常栈,可以为我们工作大大提高效率,希望这篇文章给大家带来收获,最后再送给大家一个小技巧。异常栈不仅仅可以用来排查异常哦,还可以帮助大家学习源码,debug 源码找不到入口怎么办,那就创造一个异常!
本文链接:http://www.28at.com/showinfo-26-112730-0.html盘点 Mybatis 使用过程中遇到的坑!
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com