背景

近日,某个系统的测试环境mybatis总是报Invalid bound statement(not found)异常,导致tomcat容器无法启动。异常信息如下:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xxx.management.dao.IssueDao.countByCid
at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:227)
at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:49)
at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:65)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58)
at com.sun.proxy.$Proxy126.countByCid(Unknown Source)
at com.xxx.management.service.issue.IssueService.countByCid(IssueService.java:81)
at com.xxx.management.service.issue.IssueService$$FastClassBySpringCGLIB$$be57e1e9.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)

QA同学开始以为是develop分支有代码改动导致,切到master分支重新部署,还是出现一样的问题,可是诡异的是相同的代码其实在2天前已经上线了,线上表现一切正常。于是开发同学(我)开始介入排查问题。

注意:两个环境的云主机配置,OS版本,JDK版本,tomcat版本完全一致。

问题定位

首先,我尝试本机复现,发现无论是develop分支还是master分支在本机都不会出现异常,甚至直接去测试环境scp war包到本地启动都无法复现。这就比较抓瞎了,于是只能根据错误日志开始假设排除。

假设1:mybatis interface 和 xml 映射代码错误

异常信息非常直观,就是mybatis查找不到 com.xxx.management.dao.IssueDao.countByCid 这个statement了,第一反应检查mybaits dao相关代码。

由于系统使用的是interface + xml的映射方式,所以先检查 xml 文件的namespace,没问题;再检查 <select> 的 id,也没问题,parameterType、resultType等属性均无问题。

google 上相关问题的解决方案还有人说改动一下maven的打包配置,但确认过xml和dao都已打进jar包,因此可以确定不是 dao interface 和 xml 的映射代码有问题。

假设2:mybatis mapper scan 配置错误

系统使用mybatis-spring集成,mybaits MapperScan 的配置如下:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.xxx.common.dao,com.xxx.management.dao"/>
</bean>

出问题的dao interface是 com.xxx.management.dao.IssueDao,根据异常的堆栈信息远程debug打断点(系统使用的mybatis版本为3.4.6)后发现抛异常的代码如下:

  • MapperMethod 227行:

  • MapperMethod 251行:

原因就在于 mapperInterface.equals(declaringClass),断点观察后发现两者的值都是com.xxx.management.dao.IssueDao,因此可以断定MapperScan的配置没有问题,basePackage正常生效了。

假设3:mybatis mapper locations 配置错误

mybatis-spring还有一项mapper locations的配置,系统中的配置如下:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations">
<array>
<value>classpath:/mybatis/common/*.xml</value>
<value>classpath:/mybatis/*.xml</value>
</array>
</property>
</bean>

先说明一下为啥mapperLocations有两个,且两者都是/mybatis目录下,是因为它们分别在两个不同的jar包里。classpath:/mybatis/common/*.xml 在依赖的一个二方库lib-commons.jar中,classpath:/mybatis/*.xml 则在自身工程的manage-moudle-dao.jar中。

从配置文件可知,mapperLocations被赋值给了SqlSessionFactoryBean.mapperLocations

查找一下this.mapperLocations的调用,发现被 SqlSessionFactoryBean.buildSqlSessionFactory()方法调用,调用代码段如下:

代码作用简单来说就是解析 xml mapper,并且解析成功后会打印一条DEBUG日志,于是去查 tomcat 启动日志,发现并没有Parsed mapper file: '[mybatis/IssueDao.xml]' 的日志,于是远程debug在遍历this.mapperLocations处,发现并未加载到 /myabtis目录下的任何文件,仅加载到了/mybatis/common目录下的文件,而 this.mapperLoactions并无其他write调用,因此可以断定问题出在mapperLoactions的spring属性赋值上。

问题分析

从上文SqlSessionFactoryBean的代码截图可以看到,mapperLocations实际类型是Resource[],这个是被spring在解析BeanDefinition时做的转换,通过的是ResourcePatternResolver接口,具体到本例上是PathMatchingResourcePatternResolver

PathMatchingResourcePatternResolver.getResources方法的代码如下:



注:常量CLASSPATH_ALL_URL_PREFIX = "classpath*:"

从代码中可知,classpath*: 会查找classpath下的所有符合条件的resource,而 classpath:则只会查找第一个符合条件的resource, 本例中使用的classpath:

因此spring应当是先加载了lib-commons.jar中的/mybatis/common/*.xml,然后根据第二个location去加载/mybatis/*.xml,因为第一个location的配置中也有/mybatis/,所以根据classpath:只查找第一个符合条件的原则,直接在已命中过的lib-commons.jar中搜索/mybatis/*.xml,而没有再去搜索其他jar包。

问题解决

知道原因在哪就很好办了,有两种办法:

  1. classpath:改成classpath*:
  2. 改变xml文件路径,让两个location不会有重叠路径

经过实际验证,两种方法均有效。

扩展

问题到上面其实已经解决了,但是还有一个问题遗留着:同样的代码,甚至是同一个war包,在不同的机器上的表现为啥完全不一致?

因为已知机器的系统版本、JDK版本、tomcat版本都是完全相同的,所以我猜测这个是否和JVM的jar包加载顺序有关呢?

google一番,参考 https://my.oschina.net/ericquan8/blog/1523496 这篇文章,可知 linux 系统下 jvm 其实是优先加载 inode 值更小的 jar ,去各环境实测发现确实如此。

Mybatis-Spring扫描路径有重叠导致Invalid bound statement(not found)问题的更多相关文章

  1. mybatis替换成mybatisplus后报错mybatisplus Invalid bound statement (not found):

    项目原来是mybatis,之后由于生成代码不方便,觉得替换成mybatisplus,引入mybatisplus后,启动项目报错mybatisplus Invalid bound statement ( ...

  2. mybatis Invalid bound statement (not found)错误解决办法

    由于新版的IntelliJ IDEA不再编译source folder下的xml文件,而我们平时使用mybatis时,习惯于将*Mapper.xml文件放在与dao层.service层平级的src目录 ...

  3. Spring扫面路径配置不全导致异常 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): 的原因

    运行Junit测试类 package cn.bgodata.x.zero.service; import org.junit.Test; import org.junit.runner.RunWith ...

  4. 【spring boot Mybatis】报错:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.newhope.interview.dao.UserMapper.add

    报错如下: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.newhope.i ...

  5. Spring boot结合mybatis开发的报错:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

    错误:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found),经过排查确定是没有找到xml的原因 ...

  6. Invalid bound statement (not found)--spring boot集成mybatis

    问题: {"timestamp":"2019-07-02T10:21:32.379+0000","status":500,"err ...

  7. Mybatis 异常记录(1): Invalid bound statement (not found)

    错误信息: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.pingan.cr ...

  8. mybatis报错:Invalid bound statement (not found)

    mybatis报错:Invalid bound statement (not found)的原因很多,但是正如报错提示一样,找不到xml中的sql语句,报错的情况分为三种: 第一种:语法错误 Java ...

  9. Springboot项目下mybatis报错:Invalid bound statement (not found)

    mybatis报错:Invalid bound statement (not found)的原因很多,但是正如报错提示一样,找不到xml中的sql语句,报错的情况分为三种: 第一种:语法错误 Java ...

随机推荐

  1. Jenkins Ci系列目录

    Jenkins入门篇 1.Jenkins入门之界面概览 2.Jenkins入门之新建任务 3.Jenkins入门之导航操作 4.Jenkins入门之任务基本操作 5.Jenkins入门之执行Power ...

  2. Spring Boot 整合 Shiro实现认证及授权管理

    Spring Boot Shiro 本示例要内容 基于RBAC,授权.认证 加密.解密 统一异常处理 redis session支持 介绍 Apache Shiro 是一个功能强大且易于使用的Java ...

  3. 自定义new和delete

    #include "stdafx.h" #include <stdlib.h> #include <malloc.h> #include <iostr ...

  4. Error:Failed to resolve: com.android.support:support-annotations:26.0.2

    异常信息记录: Error:Failed to resolve: com.android.support:support-annotations:26.0.2 <a href="ins ...

  5. 浅谈hosts文件

    1.什么是hosts文件?这个文件在哪? hosts文件(域名解析文件)是将主机名映射到IP地址的一个纯文本文件,原始名称是HOSTS.TXT(IP,Internet Protocol,Interne ...

  6. dede:channelartlist currentstyle高亮显示

    我们在用DEDECMS建站时,常常会做二级栏目的功能,既要用到二级栏目,也就要通过DEDE标签再套标签的方式来实现调用,而DEDECMS多层标签调用只支持channelartlist,也就是说我们只能 ...

  7. 牛客第三场 J LRU management

    起初看到这道题的时候,草草就放过去了,开了另一道题,结果开题不顺利,总是感觉差一点就可以做出来,以至于一直到最后都没能看这道题qaq 题意:类似于操作系统上讲的LRU算法,有两个操作,0操作代表访问其 ...

  8. Python 之父撰文回忆:为什么要创造 pgen 解析器?

    花下猫语: 近日,Python 之父在 Medium 上开通了博客,并发布了一篇关于 PEG 解析器的文章(参见我翻的 全文译文).据我所知,他有自己的博客,为什么还会跑去 Medium 上写文呢?好 ...

  9. Java 添加、验证PDF 数字签名

    在设置文档内容保护的方法中,除了对文档加密.添加水印外,应用数字签名也是一种有效防伪手段.数字签名的文件比较容易验证,并且具有较高的权威性和可信度.在PDF文档中,有可直接添加或验证数字签名的功能方法 ...

  10. python 爬取豆瓣电影评论,并进行词云展示及出现的问题解决办法

    本文旨在提供爬取豆瓣电影<我不是药神>评论和词云展示的代码样例 1.分析URL 2.爬取前10页评论 3.进行词云展示 1.分析URL 我不是药神 短评 第一页url https://mo ...