因为软连接导致mybatis无法正常启动

​ 最近接手的一个项目,投产上线,遇到很多问题,其中一个问题比较有意思,记录一下。

背景

​ 简单说下背景,我们的应用是SpringBoot+Mybatis,通过内部CI工具部署不同环境,在dev、stg、uat环境都没问题,投产时启动报错,错误日志如下:

1
2
3
4
5
6
7
8
9
10
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.yicallv1.dao.AgentSkillMapper.create. please check URL [jar:file:/export/package/iim-thirdparty-prod-20211112T123635026/lib/iim-thirdparty-1.0.1.jar!/BOOT-INF/classes!/sqlmap/AgentSkillMapper.xml] and URL [jar:file:/export/server/iim-thirdparty/lib/iim-thirdparty-1.0.1.jar!/BOOT-INF/classes/sqlmap/AgentSkillMapper.xml]
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:1014) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:970) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.session.Configuration.addMappedStatement(Configuration.java:768) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:297) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:113) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:138) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:131) ~[mybatis-3.5.6.jar!/:3.5.6]
at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:121) ~[mybatis-3.5.6.jar!/:3.5.6]
... 94 common frames omitted

排查

​ 刚开始看这个问题有点懵,先后检查mapper文件名、生产jar包反编译、生产主机操作系统版本、环境等,都没有问题。后来仔细看了下报错,提示
/export/package/iim-thirdparty-prod-20211112T123635026/lib/iim-thirdparty-1.0.1.jar!/BOOT-INF/classes!/sqlmap/AgentSkillMapper.xml

/export/server/iim-thirdparty/lib/iim-thirdparty-1.0.1.jar!/BOOT-INF/classes/sqlmap/AgentSkillMapper.xml

文件重名。跟运维沟通,发现生产环境与其他环境不一致,会对jar包所在目录简历一个软连接。包实际路径为上面的路径,软连接路径为下面的路径,为了统一生产启动脚本,脚本中启动jar包的命令,都是使用软连接路径。

​ 到这里,可以分析出来,mybatis在服务启动扫描mapper文件时,应该是将真实路径、软连接路径都扫了一遍,所以所有的mapper文件才会提示有重名。

​ 再进一步分析代码,加载mapper文件的配置,代码如下:

bean.setMapperLocations(resolver.getResources("classpath*:**/sqlmap/*.xml"));

问题应该出在classpath*上,mybatis会遍历所有的classpath,其中包括了真实路径、软连接路径。至此,找到原因,并最终修改为如下:

bean.setMapperLocations(resolver.getResources("classpath:sqlmap/*.xml"));