1. 为什么需要SpringBoot3整合Mybatis?
在Java企业级开发中,持久层框架的选择直接影响着项目的开发效率和运行性能。Mybatis作为一款优秀的半自动化ORM框架,通过XML或注解配置SQL语句,既保留了SQL的灵活性,又简化了JDBC的冗余操作。而SpringBoot3作为最新一代的"约定优于配置"框架,其自动装配特性可以大幅减少样板代码。
两者的结合能带来三个核心优势:
- 开发效率提升:SpringBoot的starter机制自动处理了Mybatis所需的数据源、事务管理等基础配置
- 维护成本降低:Mybatis的动态SQL和结果集映射减少了手动拼装SQL的工作量
- 性能调优空间:保留原生SQL能力便于针对复杂查询进行深度优化
注意:虽然Spring Data JPA在简单CRUD场景下更便捷,但在需要复杂查询、存储过程调用或已有SQL需要复用的场景中,Mybatis仍是更优选择。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与项目初始化
2.1 基础环境要求
- JDK 17+(SpringBoot3强制要求)
- Maven 3.5+ 或 Gradle 7.x
- IDE推荐IntelliJ IDEA 2022.3+
- 数据库任选(MySQL 8.0演示)
2.2 创建SpringBoot3项目
使用Spring Initializr生成项目时需特别注意:
bash复制# 必须选择的依赖
- Spring Web
- Mybatis Framework
- MySQL Driver
- Lombok(简化实体类)
关键pom.xml依赖版本控制:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
踩坑提醒:SpringBoot3默认使用Jakarta EE 9+,与旧版Java EE的包路径(javax)不兼容。若遇到类找不到错误,检查是否错误引入了javax包。
3. 核心配置详解
3.1 数据源配置
application.yml标准配置模板:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
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映射文件路径type-aliases-package:实体类包路径,XML中可直接用类名map-underscore-to-camel-case:自动转换数据库字段命名风格
3.2 事务管理配置
SpringBoot已自动配置了基于注解的事务管理,只需在Service层添加注解:
java复制@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
// 业务方法
}
经验之谈:建议明确指定rollbackFor,默认只回滚RuntimeException。数据库连接池建议使用HikariCP(SpringBoot默认),性能远优于传统的DBCP。
4. Mybatis的三种使用方式
4.1 纯XML映射方式
- 创建实体类:
java复制@Data
public class User {
private Long id;
private String username;
private LocalDateTime createTime;
}
- 编写Mapper接口:
java复制@Mapper
public interface UserMapper {
User selectById(@Param("id") Long id);
}
- 创建UserMapper.xml:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="BaseResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="create_time" property="createTime"/>
</resultMap>
<select id="selectById" resultMap="BaseResultMap">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
4.2 注解方式
适合简单SQL场景:
java复制@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(property = "createTime", column = "create_time")
})
User selectById(Long id);
}
4.3 动态SQL构建
XML中使用动态SQL标签:
xml复制<select id="searchUsers" resultMap="BaseResultMap">
SELECT * FROM user
<where>
<if test="username != null">
AND username LIKE CONCAT('%',#{username},'%')
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
</where>
ORDER BY id DESC
</select>
对应Java方法:
java复制List<User> searchUsers(
@Param("username") String username,
@Param("startTime") LocalDateTime startTime);
5. 高级特性集成
5.1 分页插件实现
- 添加PageHelper依赖:
xml复制<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
- 使用示例:
java复制public PageInfo<User> getUsers(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
分页原理:通过ThreadLocal绑定分页参数,在Executor拦截器中自动修改SQL
5.2 多数据源配置
- 主数据源配置:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.primary",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
}
- 次数据源配置类似,注意移除@Primary注解
5.3 类型处理器扩展
处理Java8时间API:
java复制@MappedTypes(LocalDateTime.class)
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
LocalDateTime parameter, JdbcType jdbcType) {
ps.setTimestamp(i, Timestamp.valueOf(parameter));
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, String columnName) {
Timestamp timestamp = rs.getTimestamp(columnName);
return timestamp != null ? timestamp.toLocalDateTime() : null;
}
}
注册处理器:
yaml复制mybatis:
type-handlers-package: com.example.handler
6. 性能优化实践
6.1 二级缓存配置
- 开启缓存:
xml复制<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
- 序列化支持:
java复制@Bean
public CacheKeyGenerator cacheKeyGenerator() {
return new CustomCacheKeyGenerator();
}
缓存陷阱:集群环境需要配合Redis等集中式缓存,否则会出现数据不一致
6.2 SQL批量操作
高效批量插入:
java复制@Insert("<script>" +
"INSERT INTO user (username, create_time) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.username}, #{item.createTime})" +
"</foreach>" +
"</script>")
void batchInsert(@Param("list") List<User> users);
6.3 结果集懒加载
- 全局配置:
yaml复制mybatis:
configuration:
lazy-loading-enabled: true
aggressive-lazy-loading: false
- 关联查询示例:
xml复制<resultMap id="UserWithOrders" type="User">
<collection property="orders" column="id"
select="com.example.mapper.OrderMapper.selectByUserId"/>
</resultMap>
7. 生产环境必备技能
7.1 SQL日志打印
- 标准日志配置:
yaml复制logging:
level:
com.example.mapper: debug
- 使用p6spy打印完整SQL:
xml复制<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
配置spy.properties:
properties复制module.log=com.p6spy.engine.logging.P6LogFactory
driverlist=com.mysql.cj.jdbc.Driver
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime) | %(executionTime) | %(category) | connection %(connectionId) | %(sqlSingleLine)
7.2 代码生成器使用
Mybatis Generator配置示例:
xml复制<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/demo"
userId="root"
password="123456"/>
<javaModelGenerator targetPackage="com.example.entity"
targetProject="src/main/java"/>
<sqlMapGenerator targetPackage="mapper"
targetProject="src/main/resources"/>
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.example.mapper"
targetProject="src/main/java"/>
<table tableName="user" domainObjectName="User"/>
</context>
</generatorConfiguration>
运行命令:
bash复制mvn mybatis-generator:generate
7.3 监控与诊断
- 使用Druid监控SQL:
java复制@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean<>();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
return reg;
}
- 慢SQL监控配置:
yaml复制spring:
datasource:
druid:
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
8. 常见问题解决方案
8.1 映射失败排查
典型错误场景:
- 字段名未自动转驼峰:检查map-underscore-to-camel-case配置
- 时间类型转换异常:注册正确的TypeHandler
- 结果集包含额外字段:使用@ResultMap明确指定映射关系
8.2 事务失效分析
常见原因:
- 方法非public修饰
- 自调用问题(调用同类中的@Transactional方法)
- 异常类型不匹配(默认只回滚RuntimeException)
- 数据库引擎不支持(如MyISAM)
解决方案:
java复制// 明确指定回滚异常类型
@Transactional(rollbackFor = Exception.class)
public void businessMethod() {
// ...
}
8.3 分页插件异常
PageHelper使用禁忌:
- 必须在方法开始调用startPage
- 分页语句后立即执行查询
- 不要将分页语句放在try块中(避免ThreadLocal泄漏)
正确用法:
java复制// 正确示例
PageHelper.startPage(1, 10);
List<User> users = userMapper.selectAll();
// 错误示例
List<User> users = userMapper.selectAll();
PageHelper.startPage(1, 10); // 此时分页已失效
9. 架构设计建议
9.1 分层规范
推荐项目结构:
code复制src/main/java
├── com.example.demo
│ ├── config // 配置类
│ ├── controller // 表现层
│ ├── service // 业务层
│ │ ├── impl // 实现类
│ ├── mapper // 数据访问层
│ ├── entity // 实体类
│ └── dto // 数据传输对象
src/main/resources
├── mapper // XML映射文件
├── application.yml // 主配置
└── static // 静态资源
9.2 复杂查询处理
应对策略:
- 使用@SelectProvider动态生成SQL
- 将复杂查询拆分为多个简单查询
- 对大数据量查询实现分批处理
示例:
java复制public class UserSqlProvider {
public String searchUsers(Map<String, Object> params) {
return new SQL() {{
SELECT("*");
FROM("user");
if (params.get("name") != null) {
WHERE("username LIKE #{name}");
}
ORDER_BY("create_time DESC");
}}.toString();
}
}
9.3 微服务适配
在SpringCloud环境中:
- 使用Seata处理分布式事务
- 为Mybatis配置多租户拦截器
- 将Mapper接口声明为Feign Client需特殊处理
Seata整合关键配置:
java复制@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
}
10. 测试与验证
10.1 单元测试方案
- 内存数据库测试:
java复制@DataJdbcTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@MybatisTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
@Sql("classpath:test-data.sql")
public void testSelectById() {
User user = userMapper.selectById(1L);
assertThat(user.getUsername()).isEqualTo("test");
}
}
- 真实数据库测试:
java复制@SpringBootTest
@Transactional
@Rollback
public class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Test
public void testCreateUser() {
UserDTO dto = new UserDTO("newUser");
Long id = userService.createUser(dto);
assertThat(id).isNotNull();
}
}
10.2 性能测试要点
JMeter测试建议:
- 关注连接池配置与最大连接数的关系
- 监控GC情况,避免Mapper对象内存泄漏
- 测试不同批量操作大小的吞吐量
关键指标:
- 平均响应时间 < 200ms
- 99线 < 1s
- 错误率 < 0.1%
11. 升级迁移指南
11.1 SpringBoot2到3的变更
主要适配点:
- Jakarta EE 9包路径变更(javax → jakarta)
- 废弃的配置属性检查
- HikariCP连接池配置变化
11.2 Mybatis版本选择
版本矩阵兼容性:
| SpringBoot | MyBatis | MyBatis-Spring | MyBatis-Spring-Boot-Starter |
|---|---|---|---|
| 3.1.x | 3.5.10+ | 3.0.1+ | 3.0.2+ |
| 2.7.x | 3.5.9+ | 2.1.1+ | 2.3.0+ |
升级建议:先单独升级Mybatis核心到3.5.10+,再升级SpringBoot依赖
12. 扩展与进阶
12.1 插件开发实践
自定义分页插件示例:
java复制@Intercepts(@Signature(type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class}))
public class CustomPageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 分页逻辑实现
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
注册插件:
java复制@Bean
public CustomPageInterceptor customPageInterceptor() {
return new CustomPageInterceptor();
}
12.2 多租户实现
基于Schema的租户隔离:
java复制public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
String tenantId = TenantContext.getCurrentTenant();
if (StringUtils.isNotBlank(tenantId)) {
BoundSql boundSql = ((MappedStatement)invocation.getArgs()[0])
.getBoundSql(invocation.getArgs()[1]);
String newSql = "/*tenant:" + tenantId + "*/ " + boundSql.getSql();
resetSql(invocation, newSql);
}
return invocation.proceed();
}
}
12.3 弹性数据源路由
AbstractRoutingDataSource实现:
java复制public class TenantDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
配置类:
java复制@Bean
public DataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("tenant1", tenant1DataSource());
targetDataSources.put("tenant2", tenant2DataSource());
TenantDataSource router = new TenantDataSource();
router.setTargetDataSources(targetDataSources);
router.setDefaultTargetDataSource(defaultDataSource());
return router;
}
13. 最佳实践总结
经过多个生产项目验证的黄金法则:
-
XML与注解的平衡:
- 简单CRUD使用注解
- 复杂查询、动态SQL使用XML
- 避免在注解中拼接长SQL
-
性能关键点:
- 批量操作使用rewriteBatchedStatements=true
- 分页查询先过滤再分页
- 关联查询考虑使用懒加载
-
监控指标:
java复制// 监控Mapper方法执行时间 @Around("execution(* com.example.mapper.*.*(..))") public Object monitorMapperPerformance(ProceedingJoinPoint pjp) { long start = System.currentTimeMillis(); try { return pjp.proceed(); } finally { long cost = System.currentTimeMillis() - start; if (cost > 500) { log.warn("Slow SQL detected: {}#{} cost {}ms", pjp.getSignature().getDeclaringTypeName(), pjp.getSignature().getName(), cost); } } } -
异常处理规范:
- 自定义BusinessException区分业务异常
- 使用全局异常处理器转换Mybatis异常
- 记录SQL执行上下文便于问题排查
-
团队协作约定:
- 统一Mapper方法命名规范(selectBy/get/query/find前缀)
- XML文件与Mapper接口同名同路径
- 复杂SQL添加注释说明业务场景
14. 未来演进方向
随着云原生架构的普及,Mybatis在以下场景仍有优化空间:
-
Serverless适配:
- 连接池动态伸缩
- 冷启动优化
- 无状态Mapper实例
-
云数据库深度集成:
- 自动识别Aurora、PolarDB等特性
- 读写分离智能路由
- 分布式事务简化
-
响应式编程支持:
java复制@Repository public interface ReactiveUserMapper { @Select("SELECT * FROM user WHERE id = #{id}") Mono<User> selectById(Long id); } -
AI辅助开发:
- 根据JPA实体自动生成Mapper
- SQL性能自动优化建议
- 查询模式智能分析
15. 资源推荐
学习资料:
- 官方文档:Mybatis-Spring-Boot-Starter
- 源码分析:《Mybatis技术内幕》
- 实战案例:《SpringBoot企业级开发实战》
工具集:
- Mybatis Generator:代码生成
- Mybatis Plus:增强工具包
- Mybatis Dynamic SQL:类型安全SQL构建
社区支持:
- GitHub Issues及时响应问题
- Gitter在线交流群
- 中文社区mybatis.org.cn
16. 版本更新记录
2023关键更新:
- SpringBoot 3.1支持Jakarta EE 9+
- MyBatis 3.5.10性能提升30%
- 新增Kotlin协程支持
- 增强GraalVM原生镜像兼容性
废弃特性:
- 移除对Java 8以下版本支持
- 弃用XML配置的经典模式
- 不再维护ibatis遗留代码
17. 写在最后
在真实项目开发中,我发现这些经验特别有价值:
-
复杂查询调试技巧:
- 临时启用mybatis.configuration.log-impl=stdout
- 使用MyBatis Log Plugin插件格式化日志
- 在测试环境保留SQL历史记录
-
团队协作提效方法:
java复制// 统一分页参数处理 public PageRequest buildPageRequest(HttpServletRequest request) { int page = NumberUtils.toInt(request.getParameter("page"), 1); int size = NumberUtils.toInt(request.getParameter("size"), 10); return PageRequest.of(page - 1, size); } -
性能优化实战案例:
- 百万数据导出改用游标查询
- 列表查询禁止select *
- 定期分析慢SQL日志
-
架构设计心得:
- 持久层保持"笨拙"——避免过度抽象
- 事务边界明确划分——不在Controller开启事务
- 读写分离考虑时效性——重要操作走主库
-
异常处理经验:
- 捕获MyBatisSystemException提取根因
- 乐观锁冲突特殊处理
- 数据库约束异常友好提示
这套技术组合经过多个百万级用户项目验证,当正确使用时,既能保持开发效率,又能满足高性能要求。最重要的是遵循"简单优于复杂"的原则,避免过度设计。
