最新消息:以前的文章会整理补上,如果有时间就来坐坐吧。。。

解决MyBatis BindingException: Invalid bound statement (not found)问题

Java 麦童 362浏览 0评论

背景

有个同学在使用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)问题

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址