1. SpringBoot整合MyBatis全家桶实战指南
去年接手一个日活百万级的电商项目时,技术选型会上我们团队争论不休。有人坚持用JPA,认为ORM够简单;而我力推MyBatis系列方案,最终用"MyBatis-Plus + MyBatisX"组合实现了开发效率提升40%。这套组合拳究竟强在哪?今天我就拆解其中的技术细节和实战心得。
2. 技术栈选型逻辑解析
2.1 为什么选择这套组合?
SpringBoot的自动配置遇上MyBatis的灵活性,就像咖啡遇上奶精——单独喝也行,但混合后风味更佳。MyBatis-Plus在基础MyBatis上做了增强,而MyBatisX插件则让整个开发流程可视化。三者配合能实现:
- 快速CRUD(MyBatis-Plus的BaseMapper)
- 动态SQL生成(MyBatisX自动生成)
- 无缝事务管理(Spring声明式事务)
- 优雅的分页处理(MyBatis-Plus分页插件)
踩坑提示:新手常犯的错误是同时引入MyBatis和MyBatis-Plus的starter,这会导致配置冲突。正确做法是只引入mybatis-plus-boot-starter。
2.2 版本兼容性矩阵
| 技术组件 | 推荐版本 | SpringBoot支持范围 |
|---|---|---|
| MyBatis | 3.5.6+ | 2.x ~ 3.x |
| MyBatis-Plus | 3.5.3.1+ | 2.7.x ~ 3.1.x |
| MyBatisX插件 | 最新版 | IDEA 2021.3+ |
实测在SpringBoot 2.7.18 + JDK17环境下运行最稳定。曾用SpringBoot 3.1.5遇到泛型类型推断问题,回退到2.7.x系列解决。
3. 环境搭建与基础整合
3.1 项目初始化
使用IDEA的Spring Initializr创建项目时,勾选这些依赖:
- Lombok(减少样板代码)
- Spring Web(可选)
- MySQL Driver
- MyBatis-Plus Starter(关键!)
pom.xml关键依赖示例:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
3.2 配置文件的黄金参数
application.yml中最易出错的配置项:
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL日志输出
map-underscore-to-camel-case: true # 自动驼峰转换
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段
logic-not-delete-value: 0
logic-delete-value: 1
mapper-locations: classpath*:/mapper/**/*.xml
血泪教训:logic-delete-field如果设置错误,会导致数据被物理删除。有次线上事故就是因此丢失了用户订单记录。
4. MyBatis-Plus核心功能实战
4.1 BaseMapper的魔法
继承BaseMapper后自动获得的方法:
java复制@Repository
public interface UserMapper extends BaseMapper<User> {
// 无需写方法体即拥有:
// selectById, selectBatchIds,
// insert, updateById, deleteById等方法
}
但实际项目中我们总会扩展自定义SQL。推荐这样组织Mapper:
code复制src/main/java
└── com/example/mapper
├── UserMapper.java
└── custom
└── UserCustomMapper.java
4.2 条件构造器实战技巧
QueryWrapper的链式调用示例:
java复制QueryWrapper<User> wrapper = new QueryWrapper<>()
.select("id", "name", "age")
.like("name", "张")
.between("age", 18, 30)
.orderByDesc("create_time");
Lambda写法更安全:
java复制LambdaQueryWrapper<User> lambdaWrapper = Wrappers.lambdaQuery(User.class)
.select(User::getId, User::getName)
.ge(User::getAge, 18);
性能提示:避免在循环中创建Wrapper实例,实测在万次循环中会多消耗200ms。应该复用Wrapper对象。
4.3 分页插件优化方案
配置类中添加:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
使用时分页参数建议封装为DTO:
java复制public Page<User> getUsers(PageParam param) {
Page<User> page = new Page<>(param.getPageNum(), param.getPageSize());
return userMapper.selectPage(page, null);
}
5. MyBatisX插件高效开发
5.1 逆向工程实战
安装插件后,在IDEA侧边栏找到MyBatisX,右键数据库表选择生成:
- 实体类(含Swagger注解)
- Mapper接口
- XML映射文件
- Service层模板
生成时注意勾选这些选项:
- [x] 生成Lombok注解
- [x] 生成字段注释
- [x] 生成逻辑删除注解
- [x] 生成ActiveRecord模式
5.2 跳转与代码生成技巧
- 在Mapper方法上按Alt+Enter,可快速生成对应XML语句
- 在XML中按Ctrl+鼠标点击方法名,跳转到Java接口
- 在实体字段上按Alt+Enter,可生成条件构造器代码
效率技巧:配置Live Templates快速生成常用代码片段。比如输入"mpin"自动生成:
java复制LambdaQueryWrapper<${ENTITY}> wrapper = Wrappers.lambdaQuery(${ENTITY}.class)
.eq(${ENTITY}::get${FIELD}, ${VAL});
6. 高级特性与性能优化
6.1 多租户方案实现
通过TenantLineInnerInterceptor实现:
java复制public class MyTenantLineHandler implements TenantLineHandler {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new StringValue(UserContext.getTenantId());
}
@Override
public boolean ignoreTable(String tableName) {
return "sys_config".equals(tableName); // 系统表不过滤
}
}
6.2 二级缓存整合Redis
配置类中添加:
java复制@Bean
public MybatisRedisCache mybatisRedisCache() {
return new MybatisRedisCache("myCache", redisTemplate);
}
实体类上添加注解:
java复制@CacheNamespace(implementation = MybatisRedisCache.class)
public class User implements Serializable {
//...
}
缓存陷阱:更新操作一定要清空缓存。我们曾因忘记清缓存导致数据显示延迟,引发客诉。
7. 生产环境踩坑实录
7.1 批量插入优化
错误做法:
java复制for(User user : userList) {
userMapper.insert(user); // N+1问题
}
正确方案:
java复制SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH);
UserMapper mapper = session.getMapper(UserMapper.class);
userList.forEach(mapper::insert);
session.commit();
实测万条数据插入从12秒降到1.3秒。
7.2 枚举类型处理
推荐使用MyBatis-Plus的@EnumValue注解:
java复制public enum Gender {
@EnumValue // 标记存入数据库的值
MALE(1),
FEMALE(2);
private final int code;
}
配置类型处理器:
yaml复制mybatis-plus:
type-enums-package: com.example.enums
7.3 动态表名处理
实现动态表名拦截器:
java复制public class DynamicTableNameInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) {
// 根据参数替换SQL中的表名
}
}
8. 监控与调优方案
8.1 SQL性能分析
引入p6spy依赖:
xml复制<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
配置spy.properties:
code复制driverlist=com.mysql.cj.jdbc.Driver
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime)|%(executionTime)|%(category)|%(sql)
8.2 慢SQL监控
结合Prometheus + Grafana:
java复制@Aspect
@Component
public class SqlMonitorAspect {
@Around("execution(* com..mapper.*.*(..))")
public Object monitorSql(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - start;
Metrics.counter("sql_cost").tag("method", pjp.getSignature().getName())
.increment(cost);
return result;
}
}
这套组合拳用了一年多,从初创公司到上市公司项目都在用。最大的体会是:技术选型没有银弹,但MyBatis系列在灵活性和效率之间找到了很好的平衡点。最近在尝试结合Jimmer实现更复杂的动态查询,有机会再分享。
