背景

近日,某个系统的测试环境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. Python 为什么要继承 object 类?

    自己搬运自己在知乎上的回答,感觉破乎吃枣药丸,哪天挂了这里就是个备份. 链接:https://www.zhihu.com/question/19754936/answer/229327803 2017 ...

  2. RabbitMQ从入门到精通(三)

    目录 1. 自定义消费者使用 自定义消费端演示 2.消费端的限流策略 2.1 限流的场景与机制 2.2 限流相关API 2.3 限流演示 3. 消费端ACK与重回队列机制 3.1 ACK与NACK 3 ...

  3. Git远程版本库

    目前为止,所有的Git操作都是在一个本地版本库中.现在是时候来体验Git分布式的特性了. 说到远程版本库,大家最为熟悉的就是GitHub了,它实际上就相当于一个远程版本库,托管着所有的本地版本库的提交 ...

  4. SSM 框架集成

    1.SSM是什么? SSM是指目前最主流的项目架构的三大框架: SpringMVC : spring的 Web层框架,是spring的一个模块 Spring :容器框架 MyBatis :持久层框架 ...

  5. Error:Some file crunching failed, see logs for details

    Information:Gradle tasks [:myapp2:assembleDebug] Error:Some file crunching failed, see logs for deta ...

  6. 小代学Spring Boot之自定义Starter

    想要获取更多文章可以访问我的博客 - 代码无止境. 上一篇小代同学在Spring Boot项目中配置了数据源,但是通常来讲我们访问数据库都会通过一个ORM框架,很少会直接使用JDBC来执行数据库操作的 ...

  7. compute节点上开启服务openstack-nova-compute.service时,无法启动的解决方法

          本文前一部分为本人解决问题的过程,但最终没有解决:无奈在网上找方法时,看到有网友评论说:修改controller上的guest账号密码,再重启openstack-nova-compute. ...

  8. .NET开发框架(八)-服务器集群之网络负载平衡演示(视频)

    (有声视频-服务器集群之负载平衡-NLB演示) 观看NLB视频的童鞋,都会继续观看IIS的负载平衡教程,点击>> 本文以[图文+视频],讲解Windows服务器集群的网络负载平衡NLB的作 ...

  9. rabbitMQ_topic(五)

    主题转发器 发送到主题转发器的消息不能有任意的 routing_key - 它必须是由点分隔的单词列表.这些单词可以是任何东西,但通常它们指定与消息相关联的一些功能.几个有效的routeKey示例:“ ...

  10. 灵活使用Maven Profile

    项目中一直应用Maven的profile特性解决不同环境的部署问题.最近在尝试解决本地调试环境的时候碰到一些问题,顺便仔细研究了一下.因为项目仍然在用普通SpringMVC架构,没有切换到Spring ...