前言
这个Bug前前后后折腾了两天才找到答案,虽说不是完全两天的工作时间在调试这个问题,但是过程也确实曲折,所以做一下记录,也当做一次自我反省
背景
SpringBoot 与 MyBatis-Plus 的 pom 依赖
<!-- SpringBoot 版本 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!-- MyBatis-plus 版本 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency>
项目分模块结构
之前一直停留在 SpringBoot 整合其他框架做一些小项目,所以都是把代码放在一个工程,也没有进行分模块管理。
由于公司打算开发新产品,我想尝试在 SpringBoot 的项目来做分模块的开发(Maven工程),并且用 MyBatis-Plus 来做持久层框架,也就是将 SpringBoot 做 web模块, 实体跟Mapper 拆分到 persistence模块,并将 mapper.xml 文件放在 persistence模块 的 resources/mapper 目录下。
在项目分模块完成之后,并进行代码移植时并没有发现问题,因为此时并不需要调用到自定义 mapper.xml 中的 sql 查询数据库。但随着一步一步的完善代码,也开始需要用到 mapper.xml 的 sql 查询,此时,在调试时,发现了如下错误:
即,报找不到对应的 sql
排查原因
排除 mapper.xml 书写错误
首先排除 mapper.xml 中 namespace 或 sql 的 id 写错,因为我的开发工具下载了 MybatisX 的插件,mapper.java 与 mapper.xml 都有插件的小图标,如下图所示,
怀疑:分模块导致
其次,在先前我已经整合过 SpringBoot 与 MyBatis-Plus 单项目的工程,并没有这个问题,所以,我怀疑是因为 分模块导致的原因 导致的,所以,我想 分模块跟不分模块的工程 有区别?
区别就是,不分模块 mapper.xml 文件就在项目 classes 下的 mapper 中,而分模块后, mapper.xml 文件就在 persistence模块打出来的 jar 中 。
错误思路:修改pom文件配置resources目录
所以,发现了这点之后,就开始必应解决方案,但是刚开始我的思路是 错误的,我以为项目打包之后,没有把 persistence模块的 resources 下 的 mapper 目录打包进 jar 中 。浪费了一些时间之后,我才想清楚应该先去 persistence模块 打出来的 jar 中确认有没有将 resources 下的 mapper/mapper.xml 打包进去。
怀疑:Spring Resource时没有加载 jar 中的 mapper.xml
上面的问题教了自己一课,眼见为实 ,虽然解决 Bug 的过程中,合理的猜测是必要的,但是在有限的结果集中,验证排除一些错误的思路更为重要。就像,经常有人会问我,“为什么我运行老是报找不到类的错误”,我就说那你到 tomcat 的发布路径上看一下 jar 包有没有发不上去,这其实是一个道理的。
回到问题,为什么Spring Resource时没有加载 jar 中的 mapper.xml ?必应寻找解决方案,在必应的过程中我才想起了 Spring 的 Resource 加载文件, classpath 与 classpath* 的区别,好久没有自己手写搭项目,把这个给忘记了。
所以,此时我确信将 application.xml 中 mybatis-plus.mapper-locations 的值改为 classpath*:mapper/*Mapper.xml 即可,如下
#mapper plus mybatis-plus: mapper-locations: classpath*:mapper/*Mapper.xml
怀疑:SpringBoot 跟 MyBatis-Plus 整合的配置哪里不对吗?
上面的配置修改之后,重新运行,还是报同样的错!!!
What The Fuck!!!因为我觉得这个 bug 就是由于 classpath:mapper/*Mapper.xml Spring 的 Resource 只能扫描当前应用中的 classpath 下的 mapper 目录的 mapper.xml 的文件,所以,我又重新必应了 SpringBoot 与 Mybati-plus 的整合配置 ,说到底还是对这个框架不了解,导致走了不少弯路,而且在出问题的时候,没有坚信自己的判断,而不断的尝试必应出来的结果,使得又浪费了一些时间在这个问题上。
最后必应了一圈后,还是没有找到解决方案,而且与此相关的问题文章也不多, 我开始迷茫跟急躁了,毕竟已经浪费了这么多的时间,我还是决定先验证一下我前面深信的东西---- Spring 的 Resource 加载文件, classpath 与 classpath* 的区别 ,所以,我决定自己创建 sqlSessionFactory ,代码如下
@Bean("sqlSessionFactory") public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); sqlSessionFactory.setDataSource(datasource); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath*:mapper/*Mapper.xml"); sqlSessionFactory.setMapperLocations(resources); MybatisConfiguration configuration = new MybatisConfiguration(); configuration.setJdbcTypeForNull(JdbcType.NULL); configuration.setMapUnderscoreToCamelCase(true); configuration.setCacheEnabled(false); sqlSessionFactory.setConfiguration(configuration); //添加分页功能 sqlSessionFactory.setPlugins(new Interceptor[]{ paginationInterceptor() }); return sqlSessionFactory.getObject(); }
结果是运行通过了!!不过,这也是理所当然的,这就说明了我在 application.xml 中 mybatis-plus 的配置没生效,相当于又回到了上一个问题。到了这里,我才想到去看 mybatis-plus-boot-starter 里面的源码!!
没想到的是,源码相当容易看,首先,jar 包的目录结构如下,看名字直接选择看 MybatisPlusAutoConfiguration 这个配置类
打开 MybatisPlusAutoConfiguration 的源码,直接看 SqlSessionFactory 的创建,如下,在设置 mapper-locations 的判断处,打下断点,进行调试
并且将前面自定义创建的 SqlSessionFactory 的代码注释掉,application.yml 中配置 mapper-locations: classpath*:mapper/*Mapper.xml ,重新运行,进行源码调试,发现上图断点的逻辑,没有进到 if 的判断中去,调试的 MybatisPlusProperties 对象结果如下,
!!!What The Fuck!!!我配置的明明是 classpath*:mapper/*Mapper.xml ,怎么变成了 classpath:mapper/*Mapper.xml 可恨的是,我的思路有绕弯了!!!
怀疑:yml 的配置语法对 classpath*:mapper/*Mapper.xml 解析有问题
我都服了我自己了,但确实是这次修改让我找到了最后的答案,将原本的配置写法改成了数组,写法如下,
#mapper plus mybatis-plus: mapper-locations: [classpath*:mapper/*Mapper.xml]
因为对这种配置格式也没去深究,之前都是一对一的配置项,没有尝试过数组的写法,所以上来就运行报错了,但是报的是 application.yml 的语法格式有误。所以,将值加上引号括起来,变成
#mapper plus mybatis-plus: mapper-locations: ["classpath*:mapper/*Mapper.xml"]
重新运行,发现竟然通过了!!!源码调试的 mapperLocations 的值也是 classpath*:mapper/*Mapper.xml ,此时我以为找到了问题的所在了,就是 yml 的配置语法对 classpath*:mapper/*Mapper.xml 解析有问题 我也对此深信不疑。
结论:开发工具没有同步配置
在没写这笔记前,我还深信问题就是 yml 的语法对值带有 冒号 的解析时,需要对值用 引号 包起来。但是在写笔记做记录的过程中,需要还原场景,在调试的时候,发现并没有说 需要对带有冒号的值加上引号包起来 ,这时我才想起来,会不会是 开发工具的问题,在运行的时候,没有把修改的配置文件同步上去 。经过多次的调试,也证明了这点,有时候项目跑太久,开发工具就会突然出来发难。
搞了半天, 在 application.yml 的配置 classpath*:mapper/*Mapper.xml 是有效的!!!有效的!!!有效的!!! 相当于开发工具的作祟让我白白浪费了2天时间,但是期间也发现了自身的一下问题,太久没有遇到错误,一味的相信搜索引擎而不去思考,最后还差点 用错误的认识,否定了原本正确的东西 。
#mapper plus mybatis-plus: mapper-locations: classpath*:mapper/*Mapper.xml
在发现 application.yml配置修改后没生效的第一时间,我就应该检查发布路径的文件,或者去调试源码。中间浪费太多的时间陷入盲目的必应搜索答案,做笔记反省一下自己,安定就会退步,坚持学习,永远不要放弃思考。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。