困扰我一整天的MyBatis"Invalid bound statement"问题,原来是因为这个不起眼的注解冲突!

看似简单的配置冲突,却让我排查了整整一整天

问题起源

最近在整理一个SpringBoot项目的架构时,遇到了一个典型的MyBatis问题:

java 复制代码
Invalid bound statement (not found): com.xx.system.server.dao.SysRoleMapper.pagelist

这个错误对于MyBatis使用者来说再熟悉不过了。无非就是五种常见原因:

五种常规排查方案

第一种:检查namespace是否一致 确认mapper.xml中的namespace和实际的mapper接口完全一致。

第二种:检查方法名是否匹配 核对mapper接口中的方法名和mapper.xml中的id标签,都是pagelist,完全匹配。

第三种:检查文件是否被正确构建 清理maven,重新编译,确认target目录下确实存在对应的mapper.xml文件。

第四种:检查资源文件配置 确认mybatis-plus的mapper-locations配置正确:

yaml 复制代码
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml

第五种:检查Spring配置 仔细检查了所有相关配置,没有发现明显问题。

然而,当我试完这几种解决方案后 依旧没有解决!

意外的发现

在近乎绝望的时候,我无意中查看了项目的启动类BootstrapApplication

java 复制代码
@SpringBootApplication
@MapperScan("com.xx.system.server.dao")
public class BootstrapApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootstrapApplication.class, args);
    }
}

然后又查看了MybatisConfig配置类:

java 复制代码
@Configuration
@MapperScan("com.xx.system.server.dao")
public class MybatisConfig {
    // MyBatis-Plus 相关配置
}

问题就出在这里! 相同的包路径被@MapperScan注解扫描了两次。

问题根源分析

重复扫描的副作用

  1. Bean重复注册:同一个Mapper接口会被Spring尝试注册两次
  2. Bean名称冲突:默认情况下,Mapper接口的Bean名称是接口名的首字母小写
  3. Spring Boot 2.1+的严格机制:Spring Boot 2.1之后默认不允许覆盖Bean定义

为什么会导致"Invalid bound statement"?

在重复扫描的情况下,虽然应用可能正常启动(取决于Spring配置),但MyBatis在构建Mapper代理时可能会出现混乱,导致部分Mapper方法无法正确绑定到对应的SQL语句。

解决方案

保持配置的统一性 ,只在一个地方使用@MapperScan

方案一:只在启动类中配置(推荐)

java 复制代码
@SpringBootApplication
@MapperScan("com.xx.system.server.dao")
public class BootstrapApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootstrapApplication.class, args);
    }
}

方案二:只在配置类中配置

java 复制代码
@Configuration
@MapperScan("com.xx.system.server.dao")
public class MybatisConfig {
    // MyBatis-Plus 相关配置
}

移除另一处的@MapperScan注解即可。

经验总结

  1. 配置统一原则:相关的配置应该集中管理,避免分散在多处
  2. 关注启动类:检查问题时要特别注意启动类中的注解配置
  3. 理解框架机制:了解Spring Boot版本差异带来的行为变化
  4. 代码审查要点:在代码审查时,要特别注意重复的注解配置

配置建议

对于MyBatis扫描配置,我个人建议:

yaml 复制代码
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml
  type-aliases-package: com.xx.system.server.dao.entity
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

并在启动类中统一配置Mapper扫描:

java 复制代码
@MapperScan({
    "com.xx.system.server.dao"
})

希望我的这次排查经历能够帮助到遇到类似问题的开发者,避免在这个问题上浪费不必要的时间!

技术之路,细节决定成败!


关注我,获取更多实用技术干货和实战经验!

相关推荐
Lee川13 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川17 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i18 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有19 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有19 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫20 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫20 小时前
Handler基本概念
面试
Wect21 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼21 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼21 小时前
Next.js 企业级落地
前端·javascript·面试