MyBatis无法扫描Spring Boot别名的Bug
2017-04-14 13:49
405 查看
这个问题发生的原因比较复杂,主要条件有4个:
使用Spring Boot,并使用Spring Boot的Maven插件打包
使用MyBatis(目前最新的
将Domain配置在单独的Jar包中(例如Maven多模块)
使用
然后你会发现:在开发时直接使用IDEA执行
例如我有一个Spring Boot项目,其中分为三个Maven模块:
在SqlSessionFactoryConfig中配置SqlSesstionFactory:
在UserMapper.xml使用别名:
开发时使用IDEA启动一切都会正常运行,但是如果等到运行时通过命令行启动,将会出现以下错误信息:
这个错误的大概意思是生成Mapper时出错了,原因是无法识别
为了证明这点,我翻了一下MyBatis的源码,然后在
分别在IDEA和Jar中执行,会发现前者将会打印出
既然锁定了问题出现的地方,就可以仔细看看这是如何发生的了。查看
Boot依赖Jar包中的类。
再细化一下调用逻辑,就可以准备断点调试了:
断点调试使用
开启Debug模式运行Jar包,并且监听一个特定的端口:
IDEA端在Run -> Edit Configurations中创建一个Remote应用,填写IP和监听的端口号,然后启动就可以了。
通过断点调试我在
这是一个死循环,唯有抛出
IDEA中直接运行:
命令行运行:
之后将变量
而调用这个方法时的注释则是这样说的:
也就是说,如果扫描的文件确实在一个Jar包中,这个方法应该返回这个Jar包的URL,于是尝试一个比较粗暴的改进:
如果这个URL中含有
既然这个类可以正常工作了,只需要将它设为默认的VFS。在MyBatis的文档中写着可以通过配置文件更改
MyBatis官方的解决办法首先是推荐使用mybatis-spring-boot的
Boot的VFS实现类。或是将这个实现类添加到你的项目中,并手动配置。
为了不让这些瞎折腾白费,我决定将这整个过程发布出来,教各位在使用开源项目遇到bug时如何定(zuo)位(si),这可能也是本文的仅剩的一点价值了。
文章转自:http://www.scienjus.com/mybatis-vfs-bug/
使用Spring Boot,并使用Spring Boot的Maven插件打包
使用MyBatis(目前最新的
3.3.1版本仍有这个问题)
将Domain配置在单独的Jar包中(例如Maven多模块)
使用
SqlSessionFactoryBean.setTypeAliasesPackage指定包扫描Domain
然后你会发现:在开发时直接使用IDEA执行
main方法运行时一切正常,但是打成Jar包后使用
java -jar启动时配置的Domain别名均会失效。
例如我有一个Spring Boot项目,其中分为三个Maven模块:
scienjus ----scienjus-domain --------com.scienjus.domain.User ----scienjus-mapper --------com.scienjus.mapper.UserMapper --------UserMapper.xml ----scienjus-web --------SqlSessionFactoryConfig
在SqlSessionFactoryConfig中配置SqlSesstionFactory:
@Bean public SqlSessionFactory sqlSessionFactory() throws Exception { final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean(); sqlSessionFactory.setDataSource(dataSource()); //配置别名 sqlSessionFactory.setTypeAliasesPackage("com.scienjus.domain"); return sqlSessionFactory.getObject(); }
在UserMapper.xml使用别名:
<select id="get" resultType="User"> select * from user u where id = #{id} </select>
开发时使用IDEA启动一切都会正常运行,但是如果等到运行时通过命令行启动,将会出现以下错误信息:
org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'User'. Cause: java.lang.ClassNotFoundException: Cannot find class: User
这个错误的大概意思是生成Mapper时出错了,原因是无法识别
User这个别名,也找不到
User这个class。可以看出之前配的包扫描根本没有扫描到
com.scienjus.domain.User这个类。
为了证明这点,我翻了一下MyBatis的源码,然后在
org.apache.ibatis.type.TypeAliasRegistry的
registerAliases(String packageName, Class superType)方法中发现了MyBatis是如何通过包名扫描别名类的。直接将这部分逻辑搬到
main方法中执行试试:
public static void main(String[] args) { ResolverUtil resolverUtil = new ResolverUtil(); resolverUtil.find(new IsA(Object.class), "com.scienjus.domain"); Set typeSet = resolverUtil.getClasses(); Iterator i$ = typeSet.iterator(); while(i$.hasNext()) { Class type = (Class)i$.next(); if(!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { System.out.println(type.getName()); } } }
分别在IDEA和Jar中执行,会发现前者将会打印出
com.scienjus.domain.User,后者却无任何输出结果,说明问题出在这里。
既然锁定了问题出现的地方,就可以仔细看看这是如何发生的了。查看
ResolverUtil.find方法,其通过
VFS.getInstance().list(path)方法获得Class文件,而
VFS.getInstance()默认情况下返回的是
DefaultVFS,也就是说原因是这个类的
list方法无法扫描到Spring
Boot依赖Jar包中的类。
再细化一下调用逻辑,就可以准备断点调试了:
public static void main(String[] args) throws IOException { DefaultVFS defaultVFS = new DefaultVFS(); List children = defaultVFS.list("com/scienjus/domain"); for (String child : children) { System.out.println(child); } }
断点调试使用
java -jar启动的程序并没有想象中困难,IDEA和Eclipse都内置了非常优秀的调试工具,略微介绍一下IDEA中的使用方法:
开启Debug模式运行Jar包,并且监听一个特定的端口:
java -Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y -jar scienjus-web.jar
IDEA端在Run -> Edit Configurations中创建一个Remote应用,填写IP和监听的端口号,然后启动就可以了。
通过断点调试我在
findJarForResource发现了一块比较有意思的代码:
// If the file part of the URL is itself a URL, then that URL probably points to the JAR try { for (;;) { url = new URL(url.getFile()); if (log.isDebugEnabled()) { log.debug("Inner URL: " + url); } } } catch (MalformedURLException e) { // This will happen at some point and serves as a break in the loop }
这是一个死循环,唯有抛出
MalformedURLException异常时才会跳出循环,根据上面的注释我们可以得知,这件事是必然发生的,且会将
url指向一个想要的结果。对比一下两种方式运行时
url最后的结果:
IDEA中直接运行:
scienjus-domain/target/classes/com/scienjus/domain
命令行运行:
scienjus-web/target/scienjus-web.jar!/lib/scienjus-domain.jar!/com/scienjus/domain
之后将变量
jarUrl的值赋为
scienjus-web/target/scienjus-web.jar!/lib/scienjus-domain.jar,但是最后
listResources方法会返回
null。
而调用这个方法时的注释则是这样说的:
// First, try to find the URL of a JAR file containing the requested resource. If a JAR // file is found, then we'll list child resources by reading the JAR.
也就是说,如果扫描的文件确实在一个Jar包中,这个方法应该返回这个Jar包的URL,于是尝试一个比较粗暴的改进:
public static void main(String[] args) throws IOException { DefaultVFS defaultVFS = new DefaultVFS() { @Override protected URL findJarForResource(URL url) throws MalformedURLException { String urlStr = url.toString(); if (urlStr.contains("jar!")) { return new URL(urlStr.substring(0, urlStr.lastIndexOf("jar") + "jar".length())); } return super.findJarForResource(url); } }; List children = defaultVFS.list("com/scienjus/domain"); for (String child : children) { System.out.println(child); } }
如果这个URL中含有
jar!的标识,就直接返回这个Jar包的地址。我不太确定这样做是否有隐患,不过我只有在扫描Domain别名时会用到这个类,并且这时候是正常工作的。
既然这个类可以正常工作了,只需要将它设为默认的VFS。在MyBatis的文档中写着可以通过配置文件更改
vfsImpl属性更换VFS实现类,我这里用这个配置没有效果,原因是Spring的配置会在MyBatis配置文件之前执行,所以在读取这个配置之前
VFS.getInstall()已经实例化了。然后我给MyBatis提了个Issue,顺道还发现这个扫描不到类的Bug早在去年10月就有人提出了,也早就有解决办法了,只是需要到
3.4.1版本才会发布。
MyBatis官方的解决办法首先是推荐使用mybatis-spring-boot的
1.0.1版本,默认已经配置了一个兼容Spring
Boot的VFS实现类。或是将这个实现类添加到你的项目中,并手动配置。
为了不让这些瞎折腾白费,我决定将这整个过程发布出来,教各位在使用开源项目遇到bug时如何定(zuo)位(si),这可能也是本文的仅剩的一点价值了。
文章转自:http://www.scienjus.com/mybatis-vfs-bug/
相关文章推荐
- MyBatis无法扫描Spring Boot别名的Bug
- SpringBoot集成Mybatis时无法扫描Mapper问题
- springboot之mybatis别名的设置
- SpringBoot整合Mybatis扫描不到Mapper的问题
- 在idea里使用SpringBoot整合MyBatis时遇到的Mapper扫描不到的问题
- Maven项目中,关于Spring Boot 整合MyBatis时,Service层无法找到mapper接口的问题解决
- SpringBoot整合Mybatis无法注入dao
- spring boot集成mybatis,启动报无法创建dataSource问题
- springboot + mybatis-pagehelper 参数查询不分页的bug。。。
- Spring Boot 整合mybatis mapper扫描(坑)
- springboot整合Mybatis扫描不到mapper问题
- springboot打包jar无法扫描到.xml文件的问题
- 因默认包扫描问题导致的SpringBoot项目无法启动问题
- Springboot+mybatis实现typeAliasesPackage正则扫描package中的bean
- Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注入到Spring
- [Spring Boot Debug]在 intellij idea 中无法编译 javad代码 -source 1.6 中不支持 diamond 运算符
- Mybatis结合Spring注解自动扫描源码分析
- Spring整合Mybatis【自动扫描方式】
- Spring和Mybatis整合时无法读取properties的处理方案
- Spring配置扫描mybatis的mapper文件注意: