mybatis-plus加载多个module的mapper踩坑记录
背景
有一个多模块的项目,每个模块中都有自己的mapper.xml文件。但是在执行一次SQL查询中,mybatis却报出了下面的异常
排查过程
第一步,先检查mapper扫描是否正确
先找到这个方法的位置
可以看到包名是com.pinming.security.responsibility.mapper
检查SpringBoot启动类的注解
用通配符的方式匹配路径,可以看到这个写法没有任何问题
然后我又执行了别的mapper类的查询,发现除了第一个module下的两个mapper的自定义查询方法能够被成功映射,其他mapper的方法都会报出上述异常,可以断定确实是mybatis没有扫描到全部的mapper.xml
为了知道是哪里出了问题,我们直接开始debug源码,探究mybatis扫描mapper.xml的原理
根据报错的堆栈信息,我们定位到了报错的最终位置
代码为什么会进入到这里?
我们先来看这里的判断逻辑:ms == null
,且方法上不带Flush
这个注解,程序就会进行到我们报错的位置
首先思考,Flush这个注解是什么?不知道,也没用过,而且印象中mapper的方法不需要这个注解也能注册上去,所以问题一定出在ms == null
的问题上
那么ms
是什么?怎么来的?再往上看,它的类型是MappedStatement
,逐词翻译就是“映射的语句”,通过resolveMappedStatement
方法得到,那我们将断点加到这里,重新执行代码
进入方法内部
发现最后因为configuration.hasStatement(statementId) = false
且mapperInterface.equals(declaringClass) = true
,最后返回了null
后一个判断分支判断的时接口名称是否匹配,这里一定会匹配,前一个判断分支判断是否有这个语句,很显然没找到,那么继续debug进入前一个语句
这里我debug到buildAllStatements
方法里看了半天,发现问题不在这里,就不赘述了
这里我们发现,所有语句被映射在了mappedStatements
里
找到它被赋值的地方,我们就能知道mapper中我们自定义的方法是在哪里被映射的
它有唯一的put入口,将断点加在这里,我们重启程序,观察映射的过程
要注意这里要给断点加一个condition,ms.getId()就是方法的全路径,只看自己加的方法是怎么被映射的,因为mybatis-plus的baseMapper有很多内置的方法是通过其他方法初始化的,不要去管那些
我们根据这个堆栈列表,一点点往回看
此处省略几十分钟,中间走错了很多路。最终我们找到了这里
遍历mapperLocations
,解析每一个xml,正常来说mapperLocations
肯定会包含每个mapper.xml,那我们看看它有哪些值
居然只有meeting这个模块下的两个mapper.xml?为什么?
我们往上找一下这个mapperLocations
是怎么来的
用同样的方法,找到了它唯一被赋值的地方,打下断点,重启程序。感觉离真相越来越近了哈哈哈!
进入断点,同样通过左下角的堆栈信息不断往回找
这回只往上找了一步就发现了加载它的地方
再把断点打在properties.resolveMapperLocations()
这行,重启!
下面贴上我debug操作的每一步,红框就是我debug进入的方法体
这里是什么?一脸懵逼,我先看看最后会进到哪个分支
再进去
这里看到,spring根据我们配置的mapper.xml路径,开始搜索所有匹配的文件
走到这里不用往下看了,因为这往下的搜索都是根据locationPattern
这个入参来决定的,问题一定出在locationPattern
上
确定了之后,我们再往回一步
到这里,我们仔细阅读代码,发现一个关键的词语:all
通过猜测,我们得通过调整locationPattern
的值,让代码进入上面的分支,继续往前看看locationPattern
是怎么来的
在这里,遍历mapperLocations
得到下面的入参locationPattern
这个mapperLocations
是一个成员变量,并且我们发现MybatisPlusProperties
这个类是通过配置文件注入的
在配置文件中搜索mybatis-plus
破案了!就是这个值!
那我们要改成什么值呢?回到前面的判断分支
可见,我们的配置必须以classpath*:
开头
试一试
配置完之后重启,执行方法,成功!
结论
想要多个module中的mapper.xml文件都被加载到,配置文件中mybatis-plus.mapper-locations
这一项必须以classpath*:
开头,否则就只会加载匹配到的第一个module中的指定目录