Table of Contents
背景
有个同学在使用MyBatis过程中如果出现org.apache.ibatis.binding.BindingException: Invalid bound statement (not found),很大概率是mybatis中Dao接口与Mapper配置文件在做映射绑定时出现的问题,也就是说接口与xml要么是找不到要么是找到了却无法匹配。
Spring Boot采用的是 2.7.14版本,MyBatis版本为3.5.13,因为需要和前者进行集成,所以选择的starter版本如下:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
Spring Boot低版本的包排除后需要添加JPA和JDBC依赖,测试数据库选择本地安装PostgreSQL 13。
环境准备
为了排查BindingException: Invalid bound statement (not found),分析了下小同学写的TvCharacterDao和TvSeriesDao两接口:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.micboy.dao.TvCharacterDao">
<insert id="insert" parameterType="com.micboy.pojo.TvCharacter" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tv_character(name, tv_series_id, photo)
VALUES (#{name}, #{tvSeriesId}, #{photo})
</insert>
<update id="update" parameterType="com.micboy.pojo.TvCharacter">
UPDATE tv_character
SET name=#{name},
tv_series_id=#{tvSeriesId},
photo=#{photo}
WHERE id = #{id}
</update>
</mapper>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.micboy.dao.TvSeriesDao">
<insert id="insert" parameterType="com.micboy.pojo.TvSeries" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tv_series(name, season_count, origin_release)
VALUES (#{name}, #{seasonCount}, #{originRelease})
</insert>
<update id="update" parameterType="com.micboy.pojo.TvSeries">
UPDATE tv_series
SET name=#{name},
season_count=#{seasonCount},
origin_release=#{originRelease}
WHERE id = #{id}
</update>
</mapper>
问题排查
需要注意的是,低版本的MyBatis此前是不检查namespace的,本文中依赖的版本较高,如果namespace配置错误程序在启动之前就会抛出异常:
org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'com/micboy/dao/TvCharacterDao.xml'. Cause: org.apache.ibatis.builder.BuilderException: Wrong namespace. Expected 'com.micboy.dao.TvCharacterDao' but found 'com.micboys.dao.TvCharacterDao'.
at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:128) ~[mybatis-3.5.13.jar:3.5.13]
at org.apache.ibatis.builder.xml.XMLMapperBuilder.parse(XMLMapperBuilder.java:100) ~[mybatis-3.5.13.jar:3.5.13]
at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.loadXmlResource(MapperAnnotationBuilder.java:180) ~[mybatis-3.5.13.jar:3.5.13]
at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parse(MapperAnnotationBuilder.java:118) ~[mybatis-3.5.13.jar:3.5.13]
at org.apache.ibatis.binding.MapperRegistry.addMapper(MapperRegistry.java:72) ~[mybatis-3.5.13.jar:3.5.13]
at org.apache.ibatis.session.Configuration.addMapper(Configuration.java:895) ~[mybatis-3.5.13.jar:3.5.13]
at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:80) ~[mybatis-spring-2.1.1.jar:2.1.1]
at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44) ~[spring-tx-5.3.29.jar:5.3.29]
所以大概率的异常是xml无法找到导致匹配失败,检查了下小同学在resources创建的xml文件发现文件夹名称和Dao层Interface所在的包名相同,通过GET获取数据列表没有问题。
curl -H 'Content-Type:application/json' -X GET http://127.0.0.1:8080/tvseries
[
{
"id":101,
"name":"West World",
"seasonCount":10,
"tvCharacters":null,
"originRelease":"2016-10-02"
},
Object{...},
Object{...},
Object{...},
Object{...}
]
通过PUT修改数据则会出现Binding失败的提示:
curl -H 'Content-Type:application/json' -X PUT http://127.0.0.1:8080/tvseries/102 --data '{"id":102,"name":"Person of Interest","seasonCount":5,"tvCharacters":null,"originRelease":"2011-09-22"}'
{
"timestamp":"2023-08-07",
"status":500,
"error":"Internal Server Error",
"trace":"org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.micboy.dao.TvSeriesDao.update\n\tat org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>
.......
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:829)\n",
"message":"Invalid bound statement (not found): com.micboy.dao.TvSeriesDao.update",
"path":"/tvseries/102"
}
翻看代码确认没有问题,xml文件编写确认没有问题,重点放在了resource下文件夹的目录结构了,抱着试一试的态度重新创建和Dao接口相同的目录结构:

名字相同居然二者同时存在,相信明眼的你已经看出来了,小同学将xml文件所在的目录名称建的有点迷惑,如果没看出来也没关系我们从文件夹列表视图来看下:

正常的目录结构应该是按照层级关系来进行组织,但是小同学创建的文件夹只有一层,名字为“com.micboy.dao”,好家伙一步到位。问题确认后解决方法也就确认了。第一种解决方式是按照标准层级结构来组织,第二种解决方式是通过mybatis.mapper-locations参数来指定xml位置。为了验证该选项的有效性,删除刚才创建的正确的目录组织结构,通过参数指向小同学创建的xml位置:
mybatis.mapper-locations=classpath:/com.micboy.dao/*.xml
重启Spring Boot应用。
curl -H 'Content-Type:application/json' -X PUT http://127.0.0.1:8080/tvseries/102 --data '{"id":102,"name":"Person of Interest","seasonCount":5,"tvCharacters":null,"originRelease":"2011-09-22"}'
发送修改请求后,返回数据如下:
{
"id":102,
"name":"Person of Interest",
"seasonCount":5,
"tvCharacters":null,
"originRelease":"2011-09-22"
}
可以看到update操作已经正常。当然,实际生产过程中如果使用PUT和DELETE需要做到充足的安全防范,本文举例是为了方便理解排查问题思路和常见错误分析。
转载请注明:麦童博客 » 解决MyBatis BindingException: Invalid bound statement (not found)问题