1. MyBatis-Plus高效开发全攻略:从入门到精通
最近在项目重构时,我全面引入了MyBatis-Plus(简称MP)作为持久层框架,开发效率直接提升了50%以上。作为一个深度使用者,我想分享这套框架的高效开发实践。不同于官方文档的基础介绍,本文将重点讲解实际项目中那些能真正提升生产力的技巧和避坑经验。
MP作为MyBatis的增强工具,通过内置通用Mapper和Service,让单表CRUD操作几乎零SQL。但很多人只停留在基础用法,忽略了它的动态SQL、多租户、性能优化等进阶特性。本文将系统梳理MP的核心价值点,并针对3.5.x最新版本分享实战经验。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心特性与项目适配
2.1 为什么选择MyBatis-Plus?
在微服务架构下,我们选择MP主要基于三点考量:
- 开发效率:自动生成CRUD代码,减少70%以上的重复SQL编写
- 维护性:统一的查询条件构造器,避免SQL拼接导致的注入风险
- 扩展性:插件机制完美支持多租户、逻辑删除等企业级需求
对比原生MyBatis,MP在简单查询场景下能减少80%代码量。例如获取用户列表的分页查询,传统方式需要定义XML、写分页逻辑,而MP只需:
java复制Page<User> page = new Page<>(1, 10);
userMapper.selectPage(page, Wrappers.<User>query().eq("status", 1));
2.2 版本选型建议
当前主流版本线:
- 3.5.x(推荐):支持JDK8+,包含最新动态表名等特性
- 3.4.x(稳定):适合保守型项目
- 3.3.x(不推荐):已停止维护
特别注意:SpringBoot2.x需配合MP3.5.3以下版本,SpringBoot3.x需要MP3.5.3+
3. 高效开发实践详解
3.1 实体类设计规范
实体类是MP运作的核心,常见问题包括:
java复制// 错误示范 - 缺少必要注解
public class User {
private Long id;
private String name;
}
// 正确写法
@Data
@TableName("sys_user") // 显式指定表名
public class User {
@TableId(type = IdType.AUTO) // 明确主键策略
private Long id;
@TableField("user_name") // 字段映射
private String name;
@TableLogic // 逻辑删除标记
private Integer deleted;
}
关键点:
- 永远显式声明
@TableId,避免不同数据库自增策略差异 - 字段名与列名不一致时强制使用
@TableField - 逻辑删除字段推荐用
Integer类型而非Boolean
3.2 条件构造器高级用法
QueryWrapper和LambdaQueryWrapper是MP的灵魂组件。推荐使用Lambda方式避免魔法值:
java复制// 传统方式 - 易出错
wrapper.eq("status", 1);
// Lambda方式 - 类型安全
wrapper.lambda().eq(User::getStatus, 1);
复杂查询示例:
java复制wrapper.lambda()
.ge(User::getCreateTime, startDate)
.le(User::getCreateTime, endDate)
.nested(w -> w.like(User::getName, "张").or().like(User::getEmail, "zhang"))
.orderByDesc(User::getId);
3.3 动态SQL处理技巧
MP的动态SQL能力常被低估。实际项目中可以这样优化:
java复制public List<User> queryUsers(UserQuery query) {
return lambdaQuery()
.eq(query.getId() != null, User::getId, query.getId())
.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName())
.list();
}
经验:条件方法第一个参数为boolean类型,当false时自动忽略该条件
4. 企业级功能实现
4.1 多租户方案实践
多租户是SAAS系统的刚需。MP通过TenantLineInnerInterceptor提供开箱即用的支持:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(
new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new StringValue("当前租户ID");
}
}
));
return interceptor;
}
动态取消租户隔离技巧:
java复制// 在特定Mapper方法上排除租户过滤
@InterceptorIgnore(tenantLine = "true")
List<User> selectAllWithoutTenant();
4.2 数据权限控制
结合MP的SQL注入机制,可以实现行列级数据权限:
java复制public class DataPermissionInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) {
// 修改SQL添加权限过滤条件
String newSql = boundSql.getSql() + " AND dept_id IN (1,2,3)";
resetSql(ms, boundSql, newSql);
}
}
5. 性能优化指南
5.1 批量操作优化
错误做法:
java复制for (User user : userList) {
userMapper.insert(user);
}
正确方案:
java复制// 批量插入
userMapper.insertBatchSomeColumn(userList);
// 批量更新
userMapper.updateBatchById(userList);
实测:批量插入1万条数据,循环插入耗时12秒,批量方式仅需1.8秒
5.2 SQL执行监控
通过PerformanceInterceptor监控慢查询:
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
sql-parser-cache: true # 开启SQL解析缓存
建议配合自定义拦截器记录超过500ms的查询:
java复制public class SlowSqlInterceptor implements InnerInterceptor {
private static final long SLOW_THRESHOLD = 500;
@Override
public void beforeQuery(...) {
long start = System.currentTimeMillis();
// 执行查询...
long cost = System.currentTimeMillis() - start;
if (cost > SLOW_THRESHOLD) {
log.warn("Slow SQL detected: {}ms - {}", cost, boundSql.getSql());
}
}
}
6. 常见问题排查
6.1 字段映射异常
现象:查询返回的实体类部分字段为null
排查步骤:
- 检查
@TableField注解的value是否正确 - 确认数据库字段命名规范(MP默认开启下划线转驼峰)
- 查看生成的SQL语句(开启SQL日志)
6.2 分页失效问题
典型场景:
java复制Page<User> page = new Page<>(1, 10);
userMapper.selectPage(page, wrapper);
// page.getRecords()为空但total正确
解决方案:
- 确认已添加分页拦截器:
java复制interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); - 检查wrapper是否包含GROUP BY语句(分页查询不支持)
6.3 逻辑删除不生效
必要条件:
- 实体类字段添加
@TableLogic - 全局配置逻辑删除值:
yaml复制mybatis-plus: global-config: db-config: logic-delete-value: 1 # 已删除 logic-not-delete-value: 0 # 未删除
7. 扩展开发技巧
7.1 自定义SQL注入器
实现批量更新不存在时插入(upsert):
java复制public class BatchUpsert extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(...) {
String sql = "<script>INSERT INTO %s %s VALUES %s ON DUPLICATE KEY UPDATE %s</script>";
// 构建SQL逻辑...
return addUpdateMappedStatement(msClass, modelClass, "batchUpsert", sqlSource);
}
}
// 注册到配置
public void injectSqlInjector() {
new MybatisPlusCustomSqlInjector().inspectInject(
new BatchUpsert()
);
}
7.2 动态表名处理
在分表场景下,可以通过DynamicTableNameInnerInterceptor实现:
java复制interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor() {
@Override
public String dynamicTableName(String sql, String tableName) {
return tableName + "_" + LocalDate.now().getYear();
}
});
经过多个项目的实战验证,合理使用MyBatis-Plus能显著提升开发效率。但要注意避免过度依赖自动生成SQL,复杂查询仍需手写XML。建议将MP作为基础工具,结合自定义扩展构建适合自己项目的持久层方案。
