1811 字
约 6 分钟
8
S08-J05 · MyBatis 集成全谱:Spring、Boot、注解与 SQL 构建器

适用读者:能跑通 MyBatis,但说不清"SqlSession 谁在管""注解和 XML 该怎么分工"的人。
前置:S08-B01(Mapper 为什么不用写实现类)、B02(参数与结果映射)、B03(动态 SQL)、S07(Spring IoC 与代理)。
覆盖旧文(章号版):3054(第1章 框架介绍)、3055(第2章 环境搭建)、3056(第3章 第一个程序)、3057(第4章 核心配置文件)、3058(第5章 SQL 映射)、3065(第12章 注解开发)、3066(第13章 Spring 集成)、3067(第14章 Spring Boot 集成)、3074(第21章 SQL 构建器)、3075(第22章 Boot 集成进阶)、3079(第26章 测试与调试)→ 11 章 / 约 7.4 万字符。
扩展点:E-S08-021(三种集成形态与 SqlSession 的生命周期归属)、E-S08-022(注解 vs XML vs SQL 构建器的分工判据)、E-S08-023(可测性:@MybatisTest 与 H2 的边界)。
下一篇:S08-J06 · MyBatis 进阶机制。


1. 一句话结论

MyBatis 与 Spring 集成的全部分歧,只有一个问题:SqlSession 由谁创建、由谁关闭、什么时候提交。答案确定后,剩下的(配置怎么放、注解还是 XML、事务怎么生效)都是推论。

出处:A-3066(Spring 集成)、A-3067(Boot 集成)、A-3075(Boot 进阶)。


2. 三种集成形态与生命周期归属(E-S08-021)

形态 谁管 SqlSession 事务 适用
原生(SqlSessionFactory + 手动 openSession) 你自己(try-with-resources) session.commit() 手动 学习、批处理脚本、无 Spring
Spring 集成(SqlSessionTemplate / MapperFactoryBean) Spring(线程绑定,随事务提交) @Transactional 传统 Spring 项目
Spring Boot 自动配置(mybatis-spring-boot-starter) Spring(同上)+ 自动扫描 @Mapper 同上 现代项目默认
// 原生:必须自己关心关闭与提交
try (SqlSession s = factory.openSession()) {
    UserMapper m = s.getMapper(UserMapper.class);
    m.insert(u);
    s.commit();                    // 忘了这行 → 数据不见
}

// Spring:交给容器,事务由 @Transactional 决定
@Service
public class UserService {
    private final UserMapper mapper;              // 构造注入(Spring 4.3+ 单构造免注解)
    @Transactional
    public void create(User u) { mapper.insert(u); }
}

三条硬结论:

① SqlSessionTemplate 是线程安全的;原生 SqlSession 不是 → 不要把它当字段存
② 不开事务时,每次 Mapper 调用自动提交(Spring 环境下)
③ 混用「原生 openSession」与「Spring 事务」→ 两个连接、两套事务,回滚各管各的

判据:"为什么我的 insert 没生效"九成是漏了 commit() 或事务边界写在了内层私有方法上(同类自调用不走代理,见 S07-J01)。


3. 核心配置文件与 Boot 配置的对应

原生 mybatis-config.xml Boot application.yml
<environments> 数据源 spring.datasource.*
<mappers><mapper resource=…> mybatis.mapper-locations=classpath:mapper/*.xml
<typeAliases> mybatis.type-aliases-package
<settings><setting name="mapUnderscoreToCamelCase"> mybatis.configuration.map-underscore-to-camel-case=true
<plugins> @Bean Interceptor 或 mybatis.configuration + 注册

坑 1:mybatis.configuration.* 与 mybatis-config.xml 同时存在时以 yml 为准(Boot 的自动配置会覆盖),排查"下划线转驼峰不生效"先看这里。


4. 注解 vs XML vs SQL 构建器(E-S08-022)

方式 优点 缺点 判据
注解(@Select/@Insert/@Results) 就近、无需 XML、类型安全 动态 SQL 难写(要嵌 <script>)、复杂结果映射拥挤 简单 CRUD、单表
XML 映射 动态 SQL 表达力最强、SQL 与代码分离、易 review 拼写错误运行时才暴露、两处维护 复杂查询、多表、动态条件
SQL 构建器(new SQL(){{…}} 或 @SelectProvider) 类型安全、可复用片段 可读性差(匿名子类初始化块)、复杂 SQL 反而更难读 条件组合多但结构固定
// 注解 + 动态 SQL 的折中:用 <script>
@Select("<script>SELECT * FROM user <where>" +
        "<if test='name != null'>AND name = #{name}</if></where></script>")
List<User> find(@Param("name") String name);

// SQL 构建器:适合"条件可选"的查询
String sql = new SQL() {{
    SELECT("*"); FROM("user");
    if (name != null) WHERE("name = #{name}");
}}.toString();

判据:一张表超过 10 个字段或查询有 3 个以上可选条件,就上 XML——注解里的字符串拼接是维护灾难。


5. 结果映射的三层(3057/3058 的压缩)

① 自动映射:列名 == 属性名(或开启 mapUnderscoreToCamelCase)
② 别名映射:SELECT user_name AS userName
③ 显式 resultMap:类型转换、嵌套映射、构造器注入
<resultMap id="userWithOrders" type="User">
  <id property="id" column="id"/>
  <result property="createdAt" column="created_at"
          javaType="java.time.LocalDateTime"/>
  <collection property="orders" ofType="Order"
              select="selectOrdersByUserId" column="id"/>   <!-- 嵌套查询 → N+1 -->
</resultMap>

判据:<collection select=…> 就是 N+1 的源头(见 J06 §3 的 join 替代方案);能用 <collection resultMap=…> 的 join 映射就别用嵌套查询。


6. 可测性:@MybatisTest 与 H2 的边界(E-S08-023)

@MybatisTest                                  // 只加载 MyBatis 相关 Bean,不起整个应用
@AutoConfigureTestDatabase(replace = NONE)     // 指向真实测试库
class UserMapperTest {
    @Autowired UserMapper mapper;
    @Test void findsByCondition() { … }
}
方案 优点 边界
H2/内存库 快、无外部依赖 方言差异:ON DUPLICATE KEY、窗口函数、JSON 函数、EXPLAIN 行为不同 → 测不出真实 SQL 问题
Testcontainers MySQL 方言一致、可跑真实 DDL 慢(数秒启动)、CI 需 Docker
真实测试库 最接近生产 隔离与清理成本高

判据:凡是用了 MySQL 特有语法(ON DUPLICATE KEY UPDATE、INSERT IGNORE、JSON 列、全文索引)的 Mapper,必须用 Testcontainers 或真实库测;H2 只适合纯标准 SQL 的冒烟。

调试三板:

① 日志:logging.level.<mapper 包>=DEBUG(打印 SQL 与参数)
② p6spy / datasource-proxy:打印耗时与真实参数(看清 ? 绑定了什么)
③ MyBatis 内置:configuration.setLogImpl(StdOutImpl) 在无日志框架时兜底

7. 反例:六种"集成后更难查"

现象 根因
事务回滚了但数据还在 混用原生 SqlSession(各自连接、各自提交)
@Transactional 不生效 同类自调用 / 方法非 public / 异常被吞
下划线转驼峰失效 yml 与 XML 同时配置,被覆盖
Mapper 注入失败 未加 @Mapper 且未配 @MapperScan
SQL 在 XML 改了不生效 打包时 XML 未进 target/classes(resources 配置)
批量插入慢 循环调用单条 insert(应 ExecutorType.BATCH 或 <foreach>)

8. 自测

  1. 三种集成形态下 SqlSession 谁管?为什么不要把它当字段?
  2. 注解/XML/构建器的分工判据?
  3. mybatis.configuration 与 mybatis-config.xml 冲突时谁生效?
  4. H2 测不出的问题有哪些?
  5. @Transactional 不生效的三个常见原因?
答案
  1. 自己/Spring/Spring 自动配置;原生 SqlSession 非线程安全且会话状态会被并发污染。
  2. 简单单表用注解,复杂动态查询用 XML,条件组合多结构固定用构建器。
  3. yml(Boot 自动配置覆盖)。
  4. 方言特有语法与执行计划行为。
  5. 同类自调用不走代理、非 public、异常被 catch 未抛出。

9. 下一篇

S08-J06 · MyBatis 进阶机制 —— 关联映射与 N+1、一级/二级缓存的失效条件与脏读风险、插件四大接口与责任链顺序、批量的三种写法与性能实测口径。


本篇属于系列《S08 · MyBatis 持久层》。旧文来源:A-3054–3058、A-3065–3067、A-3074/3075/3079(章号版 11 章);扩展点 E-S08-021/022/023。版本口径 MyBatis 3.5.x / mybatis-spring-boot-starter 3.x / MySQL 8.0。

S08-J05 · MyBatis 集成全谱:Spring、Boot、注解与 SQL 构建器
https://www.clxhxhhr.top/posts/5291/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。