困扰我一整天的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"
})

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

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


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

相关推荐
C雨后彩虹1 天前
任务最优调度
java·数据结构·算法·华为·面试
Chan161 天前
【 Java八股文面试 | JavaSE篇 】
java·jvm·spring boot·面试·java-ee·八股
辞砚技术录1 天前
MySQL面试题——索引2nd
数据库·mysql·面试
码农水水1 天前
中国邮政Java面试:热点Key的探测和本地缓存方案
java·开发语言·windows·缓存·面试·职场和发展·kafka
a程序小傲1 天前
小红书Java面试被问:TCC事务的悬挂、空回滚问题解决方案
java·开发语言·人工智能·后端·python·面试·职场和发展
a努力。1 天前
国家电网Java面试被问:最小生成树的Kruskal和Prim算法
java·后端·算法·postgresql·面试·linq
NAGNIP1 天前
一文搞懂机器学习中的学习理论!
算法·面试
CCPC不拿奖不改名1 天前
数据处理与分析:数据可视化的面试习题
开发语言·python·信息可视化·面试·职场和发展
柒.梧.1 天前
SSM常见核心面试问题深度解析
java·spring·面试·职场和发展·mybatis
ssshooter1 天前
复古话题:Vue2 的空格间距切换到 Vite 后消失了
前端·vue.js·面试