news 2026/5/6 0:45:43

03 MyBatis Spring Boot 集成、事务、测试与工程化体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
03 MyBatis Spring Boot 集成、事务、测试与工程化体系

本文件覆盖 MyBatis 与 Spring / Spring Boot 的工程化实践:Starter 自动配置、Mapper 扫描、事务边界、SqlSession 生命周期、测试策略、代码生成、目录规范、多环境配置、主线项目基础落地。

官方参考:

  • MyBatis Spring: https://mybatis.org/spring/
  • MyBatis Spring Boot Starter: https://mybatis.org/spring-boot-starter/mybatis-spring-boot-starter/

1. 为什么需要 MyBatis-Spring

原生 MyBatis 需要手动创建SqlSessionFactorySqlSession。Spring 集成后:

  • 由 Spring 管理SqlSessionFactory
  • Mapper 接口成为 Spring Bean。
  • 参与 Spring 事务。
  • MyBatis 异常转换为 SpringDataAccessException
  • 业务代码无需手动管理 SqlSession。

业务代码:

@ServicepublicclassUserService{privatefinalUserMapperuserMapper;publicUserService(UserMapperuserMapper){this.userMapper=userMapper;}publicUsergetUser(Longid){returnuserMapper.selectById(id);}}

2. Spring Boot 最小 Demo

Maven:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency></dependencies>

配置:

spring:datasource:url:jdbc:h2:mem:demo;MODE=MySQL;DB_CLOSE_DELAY=-1driver-class-name:org.h2.Driverusername:sapassword:mybatis:mapper-locations:classpath*:mapper/**/*.xmltype-aliases-package:com.example.mybatis.domainconfiguration:map-underscore-to-camel-case:true

启动类:

@SpringBootApplication@MapperScan("com.example.mybatis.mapper")publicclassMyBatisApplication{publicstaticvoidmain(String[]args){SpringApplication.run(MyBatisApplication.class,args);}}

3. 目录结构

src/main/java/com/example/mybatis/ ├── MyBatisApplication.java ├── controller/ ├── service/ ├── domain/ ├── dto/ ├── mapper/ └── config/ src/main/resources/ ├── mapper/ │ └── UserMapper.xml ├── schema.sql └── application.yml

职责:

  • controller:HTTP 入参和响应。
  • service:事务和业务流程。
  • mapper:数据访问接口。
  • domain:领域对象或数据库对象。
  • dto:请求和响应对象。
  • resources/mapper:SQL XML。

4. Mapper 扫描

方式一:启动类@MapperScan

@MapperScan("com.example.mybatis.mapper")

方式二:每个 Mapper 加@Mapper

@MapperpublicinterfaceUserMapper{}

大型项目推荐@MapperScan,减少重复注解。

5. Mapper XML 路径

mybatis:mapper-locations:classpath*:mapper/**/*.xml

常见错误:

  • XML 没被打包到 classpath。
  • namespace 与 Mapper 接口全限定名不一致。
  • statement id 与方法名不一致。
  • resultType 包名写错。

6. 事务边界

MyBatis-Spring 会让 MyBatis 参与 Spring 事务。

@ServicepublicclassOrderService{privatefinalOrderMapperorderMapper;privatefinalStockMapperstockMapper;@TransactionalpublicLongcreateOrder(CreateOrderCommandcommand){orderMapper.insert(command.toOrder());stockMapper.decrease(command.productId(),command.quantity());returncommand.orderId();}}

事务应放在 Service 层,而不是 Mapper 层。

7. 事务失效场景

常见失效:

  1. 同类内部方法自调用。
  2. 方法不是public
  3. 异常被捕获但未抛出。
  4. 抛出受检异常但未配置 rollback。
  5. 数据源不受 Spring 管理。

自调用反例:

publicvoidouter(){inner();// 事务可能不生效}@Transactionalpublicvoidinner(){}

解决:

  • 把事务方法放到另一个 Spring Bean。
  • 通过代理调用。
  • 使用编程式事务。

8. 只读事务

@Transactional(readOnly=true)publicUserDetailgetUserDetail(Longid){returnuserMapper.selectDetail(id);}

只读事务表达意图,并可让部分数据库或连接池做优化。但不要误以为它能绝对阻止写操作。

9. 批量操作

MyBatis 支持ExecutorType.BATCH

Spring 中可配置批量 SqlSessionTemplate,或在特定场景中使用批处理。

XML 批量插入:

<insertid="batchInsert">insert into users(username, email) values<foreachcollection="users"item="user"separator=",">(#{user.username}, #{user.email})</foreach></insert>

专家提醒:

  • 批量大小要控制。
  • 大批量注意 SQL 长度限制。
  • 批处理失败要处理部分成功。
  • 大数据导入应考虑分批提交。

10. 分页

简单分页:

<selectid="selectPage"resultType="User">select id, username, email from users order by id desc limit #{limit} offset #{offset}</select>

Mapper:

List<User>selectPage(@Param("limit")intlimit,@Param("offset")intoffset);

深分页问题:

limit20offset1000000

数据库需要跳过大量记录,性能差。

优化:

whereid<#{lastId}orderbyiddesclimit#{limit}

称为游标分页或 seek pagination。

11. 测试策略

Mapper 测试:

@MybatisTest@AutoConfigureTestDatabase(replace=AutoConfigureTestDatabase.Replace.NONE)classUserMapperTest{@AutowiredprivateUserMapperuserMapper;@TestvoidselectById(){Useruser=userMapper.selectById(1L);assertThat(user.getUsername()).isEqualTo("ada");}}

Spring Boot 集成测试:

@SpringBootTest@TransactionalclassUserServiceTest{@AutowiredprivateUserServiceuserService;@TestvoidcreateUser(){Longid=userService.createUser(newCreateUserCommand("ada","ada@example.com"));assertThat(id).isNotNull();}}

12. Testcontainers

H2 与 MySQL/PostgreSQL 行为有差异。关键 SQL 建议用 Testcontainers 跑真实数据库。

@Testcontainers@SpringBootTestclassUserMapperMysqlTest{@ContainerstaticMySQLContainer<?>mysql=newMySQLContainer<>("mysql:8.4");@DynamicPropertySourcestaticvoiddatasource(DynamicPropertyRegistryregistry){registry.add("spring.datasource.url",mysql::getJdbcUrl);registry.add("spring.datasource.username",mysql::getUsername);registry.add("spring.datasource.password",mysql::getPassword);}}

13. SQL 初始化与迁移

简单 Demo 可用:

schema.sql data.sql

生产项目建议使用:

  • Flyway。
  • Liquibase。

Flyway 示例:

src/main/resources/db/migration/V1__create_users.sql src/main/resources/db/migration/V2__add_user_status.sql

数据库结构迁移必须版本化,不应手工改库。

14. 代码生成

MyBatis Generator 或 MyBatis Dynamic SQL 可生成基础 Mapper。

适合:

  • 大量单表 CRUD。
  • 数据库表多。
  • 规范化基础代码。

风险:

  • 生成代码被手工改坏。
  • 复杂业务查询仍需手写。
  • 生成模型与领域模型混淆。

专家实践:生成代码放基础访问层,业务查询单独写,领域模型不要完全等同数据库表。

15. DTO、DO、Domain 分层

常见对象:

  • Request DTO:HTTP 入参。
  • Response DTO:HTTP 响应。
  • DO / PO:数据库表映射对象。
  • Domain:业务领域对象。
  • Command / Query:应用服务入参。

简单项目可以合并,但复杂项目应避免 Controller 直接暴露数据库对象。

16. 异常处理

MyBatis-Spring 会将异常转换为 Spring 的DataAccessException层级。

业务层不要把所有异常吞掉:

try{userMapper.insert(user);}catch(DuplicateKeyExceptione){thrownewBusinessException("用户名已存在");}

17. 日志配置

开发环境打印 SQL:

mybatis:configuration:log-impl:org.apache.ibatis.logging.stdout.StdOutImpl

生产环境建议走 SLF4J,并注意不要打印敏感参数。

logging:level:com.example.mybatis.mapper:debug

18. 主线项目 Stage 1:用户模块

目标:构建KnowledgeHub的用户模块。

表:

createtableusers(idbigintgeneratedbydefaultasidentityprimarykey,usernamevarchar(64)notnullunique,emailvarchar(128)notnull,statusvarchar(32)notnull,created_attimestampnotnull);

Mapper:

publicinterfaceUserMapper{UserDOselectById(Longid);intinsert(UserDOuser);List<UserDO>search(UserSearchQueryquery);}

Service:

@ServicepublicclassUserService{privatefinalUserMapperuserMapper;@TransactionalpublicLongcreateUser(CreateUserCommandcommand){UserDOuser=UserDO.create(command.username(),command.email());userMapper.insert(user);returnuser.getId();}}

19. 工程化知识清单

入门:

  • Starter 依赖。
  • application.yml。
  • MapperScan。
  • XML 路径。
  • Service 调 Mapper。

进阶:

  • Spring 事务。
  • Mapper 测试。
  • 分页。
  • 批量插入。
  • SQL 初始化。

高级:

  • Testcontainers。
  • Flyway/Liquibase。
  • 异常转换。
  • DTO/DO 分层。
  • 代码生成治理。

精通:

  • 多环境配置。
  • 连接池。
  • 批处理策略。
  • 深分页优化。
  • 日志脱敏。

专家:

  • 数据访问层架构。
  • 事务边界治理。
  • 多数据源。
  • 数据迁移规范。
  • SQL 审查流程。

20. 面试题与完整答案

20.1 MyBatis-Spring 的作用是什么?

它把 MyBatis 集成到 Spring 容器中,让 Mapper 成为 Spring Bean,让 SqlSession 参与 Spring 事务,并把 MyBatis 异常转换为 Spring DataAccessException。业务代码不需要手动管理 SqlSession。

20.2@MapperScan@Mapper如何选择?

少量 Mapper 可用@Mapper。大型项目推荐@MapperScan扫描包,减少重复注解,统一管理 Mapper 注册路径。

20.3 事务应该放在哪一层?

事务应放在 Service 或应用服务层,因为事务代表业务操作边界。Mapper 只负责单条或少量 SQL,不应决定业务事务范围。

20.4 Spring 事务为什么会失效?

常见原因包括同类内部自调用、方法非 public、异常被捕获、受检异常未配置 rollback、对象不是 Spring Bean、数据源不受 Spring 管理。核心原因是没有通过 Spring 事务代理执行。

20.5 为什么关键 SQL 测试不建议只用 H2?

H2 与 MySQL/PostgreSQL 在 SQL 方言、索引、锁、时间类型、分页、JSON、关键字等方面可能不同。关键 SQL 应使用 Testcontainers 跑真实数据库,降低上线风险。

20.6 批量插入有哪些风险?

SQL 过长、参数过多、锁时间长、失败后部分成功、内存占用高。应控制批量大小,分批提交,并设计失败处理策略。

20.7 深分页为什么慢?

limit offset的 offset 很大时,数据库需要扫描并跳过大量记录。优化方式是基于索引字段做游标分页,如where id < lastId order by id desc limit size

20.8 为什么要用 Flyway 或 Liquibase?

生产数据库结构需要版本化、可审计、可回滚和可重复部署。手工改库不可追踪,容易导致环境不一致。Flyway/Liquibase 能把数据库变更纳入工程流程。

21. Spring Boot 自动配置机制

MyBatis Spring Boot Starter 通常会自动配置:

  • SqlSessionFactory
  • SqlSessionTemplate
  • Mapper 扫描支持
  • MyBatis 配置属性绑定

常用属性:

mybatis:config-location:classpath:mybatis-config.xmlmapper-locations:classpath*:mapper/**/*.xmltype-aliases-package:com.example.domaintype-handlers-package:com.example.mybatis.typehandlerconfiguration:map-underscore-to-camel-case:true

如果同时配置config-locationconfiguration,要注意配置来源冲突。团队应统一一种配置风格。

22. SqlSessionTemplate

Spring 中不直接使用原生SqlSession,而是使用线程安全的SqlSessionTemplate

它负责:

  • 获取当前事务绑定的 SqlSession。
  • 提交或回滚交给 Spring 事务。
  • 关闭 SqlSession。
  • 异常转换。

业务代码通常不直接注入SqlSessionTemplate,而是注入 Mapper。

23. 事务传播行为

常见传播:

  • REQUIRED:默认,加入当前事务,没有则新建。
  • REQUIRES_NEW:挂起当前事务,新建事务。
  • NESTED:嵌套事务,依赖 savepoint。
  • SUPPORTS:有事务就加入,没有也可执行。

示例:

@TransactionalpublicvoidcreateOrder(){orderMapper.insert(order);auditService.writeAuditLog();// 如果内部 REQUIRES_NEW,日志独立提交}

专家提醒:REQUIRES_NEW会额外占用连接,滥用可能导致连接池耗尽。

24. 回滚规则

Spring 默认对 RuntimeException 和 Error 回滚,对 checked exception 不回滚。

@Transactional(rollbackFor=Exception.class)publicvoidimportUsers(Filefile)throwsIOException{// ...}

不要吞异常:

try{mapper.insert(row);}catch(Exceptione){log.error("failed",e);}

吞掉异常会让事务认为执行成功。

25. Service 方法设计

好的 Service 方法:

  • 表达业务用例。
  • 控制事务。
  • 调用一个或多个 Mapper/Repository。
  • 做权限和业务校验。
  • 不拼 SQL。
@TransactionalpublicLongenrollCourse(LonguserId,LongcourseId){CourseDOcourse=courseMapper.selectByIdForUpdate(courseId);if(!course.canEnroll()){thrownewBusinessException("课程不可报名");}enrollmentMapper.insert(userId,courseId);courseMapper.increaseLearnerCount(courseId);returncourseId;}

26. Mapper 测试数据准备

推荐每个测试显式准备数据:

@Sql("/sql/user-fixture.sql")@MybatisTestclassUserMapperTest{}

或使用 Testcontainers + Flyway 初始化。

测试应覆盖:

  • 正常查询。
  • 空结果。
  • 动态条件组合。
  • 插入主键回填。
  • 唯一约束冲突。
  • 复杂 ResultMap。

27. 工程规范补充

Mapper 方法命名:

  • selectById
  • selectPage
  • searchByCondition
  • insert
  • updateSelective
  • deleteById

业务语义查询:

  • selectPublishedCourses
  • selectUserLearningProgress
  • selectOrderSummary

不要:

  • query1
  • list
  • getData
  • selectMap

28. 多模块项目组织

knowledge-api knowledge-application knowledge-domain knowledge-infrastructure

MyBatis Mapper 可放 infrastructure:

knowledge-infrastructure/ ├── mapper/ ├── repository/ └── persistence-object/

领域层不依赖 MyBatis。

29. 工程化专家题补充

29.1 为什么事务方法不应包含远程调用?

远程调用耗时不可控,会延长数据库连接和锁持有时间,增加死锁和连接池耗尽风险。应尽量在事务外完成远程调用,或使用本地消息表、事件、最终一致性方案。

29.2 Mapper 测试为什么要覆盖动态 SQL 分支?

动态 SQL 的错误通常只在特定条件组合下出现,如多余and、空集合、缺少 where、参数名错误。覆盖不同分支可以提前发现生产 SQL 错误。

29.3 代码生成如何治理?

生成代码应可重复生成,避免手工修改生成文件。业务扩展写在独立 Mapper 或扩展文件中。生成器配置纳入版本管理,生成代码和手写代码边界清晰。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/6 0:38:44

Cloudpods:统一多云管理与AI应用部署的开源云管平台实践

1. 项目概述&#xff1a;一个云上的云 如果你和我一样&#xff0c;在运维和开发岗位上摸爬滚打了十几年&#xff0c;从物理机、虚拟机一路走到公有云和容器时代&#xff0c;那你一定对“多云”和“混合云”这两个词又爱又恨。爱的是它们带来的灵活性和避免供应商锁定的可能性&…

作者头像 李华
网站建设 2026/5/6 0:38:43

如何实现Windows与Office智能激活:KMS_VL_ALL_AIO完整解决方案指南

如何实现Windows与Office智能激活&#xff1a;KMS_VL_ALL_AIO完整解决方案指南 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO KMS_VL_ALL_AIO是一款专业的智能激活脚本工具&#xff0c;为Windo…

作者头像 李华
网站建设 2026/5/6 0:37:04

低代码无代码与自动化测试:AI如何让测试能力下沉到全员

低代码/无代码与自动化测试&#xff1a;AI如何让测试能力下沉到“全员”&#xff08;研发、业务、运营都能用&#xff09;在多数企业里&#xff0c;测试能力是稀缺资源&#xff1a;测试人员有限、需求迭代密集、回归窗口越来越短。于是很多团队陷入“质量焦虑”&#xff1a;想提…

作者头像 李华