第22章 MyBatis / MyBatis-Plus 常见异常与 SQL 调试

第22章 MyBatis / MyBatis-Plus 常见异常与 SQL 调试

22.1 BindingException: Invalid bound statement (not found)

根本原因排查清单(按命中概率排序):

  1. Mapper XML 文件没有被扫描到 :检查 mybatis.mapper-locations 配置的路径是否真的覆盖到了 XML 文件所在目录(尤其常见于 Maven 多模块项目,XML 放在 src/main/resources 下但 mapper-locations 只配置了主模块路径,遗漏了子模块)。
  2. XML 的 namespace 和 Mapper 接口的全限定类名不一致:哪怕只是大小写不同或者包路径打错一个字符,都会导致找不到绑定关系。
  3. 方法名在接口和 XML 里不一致<select id="selectById"> 必须和接口方法名 selectById 完全一致(区分大小写)。
  4. 注解方式(@Select 等)和 XML 方式混用时配置冲突:同一个方法同时有 XML 定义和注解定义,会导致绑定行为不确定。

排查手段: 开启 MyBatis 的 debug 日志,观察启动阶段的 Mapper 加载过程:

yaml 复制代码
logging:
  level:
    org.mybatis: debug
    org.apache.ibatis: debug

22.2 TooManyResultsException

java 复制代码
@Select("SELECT * FROM user WHERE status = #{status}")
User selectOne(String status); // 如果 status 对应多条记录,抛 TooManyResultsException

根本原因: selectOne 语义上约定"最多返回一条结果",MyBatis 在拿到结果集后如果发现行数 > 1,主动抛出这个异常(而不是悄悄只返回第一条,掩盖潜在的数据问题)。

解决方案: 如果业务上确实可能返回多条,改用 List<User> selectList(...);如果业务上"理论上只应该有一条"但抛出了这个异常,说明数据本身出现了不符合预期的重复,应该去排查数据层面的问题(是否遗漏了唯一索引约束),而不是简单地把返回类型改成 List 掩盖过去。

22.3 SQL 调试实战:从"看不懂 MyBatis 报错"到"看到真实执行的 SQL"

MyBatis 抛出的 PersistenceException 往往会包一层 Cause: java.sql.SQLSyntaxErrorException 之类的底层 JDBC 异常,直接看 MyBatis 层的报错经常不知所措,最有效的排查方式是拿到真正拼接执行的 SQL,直接在数据库客户端里重放

yaml 复制代码
# 方式1:开启对应 Mapper 包的 debug 日志,会打印出实际执行的 SQL 和参数(分两行打印,需要手动拼接)
logging:
  level:
    com.example.mapper: debug
sql 复制代码
==>  Preparing: SELECT * FROM user WHERE id = ? AND status = ?
==> Parameters: 1(Integer), ACTIVE(String)
xml 复制代码
<!-- 方式2:MyBatis-Plus 提供的 p6spy / druid 的 filters=stat 也能拿到完整可执行 SQL(含参数已经替换进去,不需要手动拼接) -->

核心原则:MyBatis/JDBC 层面的报错,第一步永远是先拿到"真正被数据库执行的那条完整 SQL",再拿这条 SQL 直接到数据库客户端(Navicat/DataGrip/命令行)里执行验证,绝大多数问题(字段名拼写错误、类型不匹配、关联表写错)在数据库客户端直接执行时都会给出比 MyBatis 包装后的异常更直接的报错信息。

22.4 MyBatis-Plus 特有异常

java 复制代码
// MybatisPlusException: 多次调用 lambda 表达式条件构造器时字段解析失败
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(User::getName, "test"); // 如果 User 类没有对应字段的 getter 方法引用能正确解析,可能抛异常

// 乐观锁更新失败(不直接抛异常,但需要注意返回值)
int rows = userMapper.updateById(user); // 如果 @Version 字段值和数据库当前值不一致,rows 返回 0 而非抛异常
if (rows == 0) {
    throw new OptimisticLockException("数据已被其他事务修改,请刷新后重试");
}

乐观锁场景是一个容易被忽视的"隐性异常" :MyBatis-Plus 的乐观锁插件在版本冲突时不会主动抛异常 ,只是让 update 语句实际影响 0 行,如果业务代码没有检查返回的受影响行数,会误以为更新成功,实际上数据完全没有被修改------这是一类"该抛异常但没抛"导致业务逻辑静默出错的典型场景,需要在封装 Service 层方法时统一检查更新受影响行数。


相关推荐
用户8181870627461 小时前
第23章 JPA / Hibernate 异常
后端
雪隐1 小时前
个人电脑玩AI-15让5060 Ti给你打工——MiniMax H3 本地部署实录:一个自带录音棚的视频模型,和它的 NVFP4 瘦身奇遇
前端·人工智能·后端
玖石书3 小时前
ASP.NET Core 迁移至 Spring系列:类库框架篇
java·后端·asp.net
PieroPC3 小时前
Windows 驱动备份与恢复工具 CMD bat
后端
Csvn3 小时前
📊 SQL 入门 Day 12: 窗口函数基础
后端·sql
玖石书3 小时前
ASP.NET Core 迁移至 Spring系列:编译生态篇
后端·spring·asp.net
Java技术小馆3 小时前
AI 如何理解对话
后端
小满zs3 小时前
Go语言第五章(数组和切片)
后端·go
AskHarries4 小时前
RSS 有必要做吗
后端