
不知道你有没有经历过这种场景Spring Boot项目里引入MyBatispom文件加依赖application.yml写几行配置跑通一个CRUD接口然后理所当然地以为“整合完成了”。结果上线后才遇到动态SQL条件不生效、缓存数据不更新、多数据源切换失灵、分页结果莫名丢失才发现“会跑”和“会用”之间隔着一大片实际操作经验。Spring Boot整合MyBatis是Java后端最经典的基础组合但正因为太基础很多人反而只停留在“能查出数据”的层面。这篇内容我打算把Spring Boot中使用MyBatis的主线完整过一遍从环境搭建、版本兼容、XML映射文件、动态SQL到缓存、自动装配、初始化原理、多数据源和面试高频题全部串联起来。既适合刚入门的同学照着复现也适合已经写了一两年CRUD、想系统补一补底层原理的开发者。1. 为什么Spring Boot项目里MyBatis是绕不开的选择1.1 与Spring Data JPA的路线之争Spring Boot官方文档中默认推荐的是Spring Data JPA我在不同团队里见过无数次“JPA还是MyBatis”的争论。JPA对单表CRUD确实很省事实体关系稳定、不需要手写SQL的小项目用它开发效率极高。但一旦进入互联网项目常见的聚合查询、多表关联统计、复杂结果映射JPA的Specification、Query DSL写出来往往比原生SQL还难读而且执行计划不在掌控范围内想调整一个join方式或者换个索引策略都要绕框架一层。MyBatis的核心优势是“SQL所见即所得”。要左连接就直接写LEFT JOIN要子查询就写子查询要按条件拼SQL就拼。DBA拿到XML文件可以直接评审团队里资历尚浅的同学也能快速读懂一段复杂查询在做什么。这正是国内大量Spring Boot项目最终选择MyBatis作为持久层框架的原因说直白点就是“可控性优先于省事”。1.2 半自动ORMMyBatis的定位和边界MyBatis被称作半自动ORM这个“半”字很关键。它帮你处理了JDBC连接、Statement、ResultSet到对象的映射这些脏活但SQL必须由开发人员自己写。听上去像是缺点实际反而是优势自动化的部分正是容易出错且没有业务价值的重复劳动而SQL部分是查询性能的核心理应交给人工精细控制。所以我的观点一直是不要拿MyBatis去和JPA比较“谁更高级”。它们是两种路线MyBatis适合希望精确控制SQL的执行过程JPA适合希望框架帮你完成大部分关系映射。项目选型时组员平均水平、表结构复杂度、DBA参与度这三个因素比“哪个框架更流行”重要得多。1.3 从大家搜索的内容看真实痛点有段时间我观察了一下和Spring Boot、MyBatis相关的搜索词发现非常有规律mybatis缓存、二级缓存实现、xmlconfigbuilder初始化原理、条件不生效、springboot版本太高、配置打印SQL、mybatis面试题。这些词基本可以画出一条从入门到进阶的路径。配置怎么打印SQL说明有人已经跑起来但排查问题看不到执行语句条件不生效说明在动态SQL的if、where上踩了坑缓存说明开始考虑查询性能优化xmlconfigbuilder和初始化原理说明想深入底层了版本太高说明环境适配出了问题。这篇文章接下来就沿着这条路径展开把这些点一个一个说清楚。2. 环境搭建先认清版本Spring Boot版本与MyBatis的兼容关系2.1 依赖坐标与版本匹配在pom里引入MyBatis依赖很简单但版本匹配是一个看似基础、实际很容易翻车的点。dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency注意这个starter版本号的第一位数字要和Spring Boot大版本对齐。Spring Boot 2.x用mybatis-spring-boot-starter 2.3.xSpring Boot 3.x用3.0.x。为什么这么严格因为starter内部打包了spring-boot-autoconfigure和mybatis-spring两个组件组合决定了自动装配代码是否兼容当前Spring Boot版本。Spring Boot 3.x一个比较大的变化是把javax替换成了jakarta命名空间如果你项目里还有旧版MyBatis或老驱动依赖javax.*一启动就是NoClassDefFoundError。热词里经常出现“springboot版本太高”大多数情况不是Spring Boot本身有问题而是配套组件没跟上。我见过一个项目从2.7升到3.2只改了parent版本结果启动直接报错排查到最后就是mybatis-spring-boot-starter还是2.x。2.2 最小配置与SQL日志打印依赖引入后application.yml里有几个配置项是每次都要写清楚的。spring: datasource: url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl逐个说作用。mapper-locations指定XML映射文件的位置classpath:mapper/**/*.xml表示resources目录下mapper文件夹里所有XML文件路径写错最常见的结果是启动不报错但一调用Mapper就提示Invalid bound statement。type-aliases-package用来配置实体类包名写了它之后XML里的resultType可以直接写User而不是com.example.demo.entity.User。map-underscore-to-camel-case解决数据库下划线字段和Java驼峰属性的映射比如user_name映射到userName不写这一行查出来的对象属性全是null。log-impl配置成StdOutImpl后SQL会在控制台直接打印出来这就是热词里“mybatis配置打印”的答案。但生产环境不建议这么干日志量太大。更优雅的做法是只对mapper包开启debug日志logging: level: com.example.demo.mapper: debug这样既能看到SQL又不会把整个应用的日志刷爆。2.3 第一个Mapper的正确姿势Mapper接口的定义有固定套路先看接口Mapper public interface UserMapper { User selectById(Param(id) Long id); }再看XML?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.UserMapper select idselectById resultTypeUser select id, user_name, email from user where id #{id} /select /mapper三个关键点namespace必须写接口的全限定名少一个字符都绑定不上select标签的id就是方法名resultType对应返回类型。如果数据库字段和Java属性对不上要么靠map-underscore-to-camel-case自动转换要么写resultMap否则查询结果看起来就是“查到了但全是null”。接口上可以直接用Mapper注解也可以在启动类上加MapperScan指定包扫描。两者同时用不会报错但我的习惯是全局用一个MapperScan就够了接口上不再重复加Mapper保持代码整洁。3. XML映射文件动态SQL与参数传递的实战经验3.1 XML还是注解怎么选MyBatis支持在接口上直接写注解SQL短查询确实很清爽。但我的建议很简单SQL超过三行或者包含动态条件就放XML。原因有三。第一XML里可以多行排版、随意写注释Git diff对比起来比注解里一长串字符串清晰得多。第二动态SQL标签比如if、where、foreach在XML里才最好用虽然注解里也有