1. MyBatis-Plus初探:为什么选择它?
第一次接触MyBatis-Plus是在2018年接手一个电商后台项目时。当时团队还在用原生MyBatis,每次新增一个简单的CRUD接口都要重复编写大量模板代码,Mapper.xml文件膨胀到难以维护的程度。直到某天架构师扔给我一个GitHub链接:"试试这个,能让你的开发效率提升50%"。这就是我与MyBatis-Plus的初次邂逅。
MyBatis-Plus(简称MP)本质上是对MyBatis的增强工具包,它保留了MyBatis的所有特性,同时通过内置通用Mapper和Service,将开发者从繁琐的SQL编写中解放出来。最直观的感受是:原先需要写20行代码的查询,现在用Wrapper两三行就能搞定。根据我的项目统计,使用MP后DAO层代码量平均减少60%,特别是对于单表操作,几乎可以做到零SQL。
注意:虽然MP能大幅简化开发,但并不意味着要完全替代MyBatis。复杂多表关联查询时,仍然需要手写XML或注解SQL。
2. 环境搭建与基础配置
2.1 项目依赖引入
以Maven项目为例,需要在pom.xml中添加核心依赖。这里特别强调版本选择的重要性——我曾因盲目使用最新版踩过坑。目前(2023年)生产环境推荐使用3.5.3.x稳定版:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>如果是非Spring Boot项目,需要额外配置MyBatis原生依赖。Spring Boot项目则会自动配置数据源和事务管理器,这也是我推荐Spring Boot用户直接使用starter的原因。
2.2 基础配置项解析
在application.yml中,MP有几个关键配置需要关注:
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL日志打印 global-config: db-config: id-type: auto # 主键策略 logic-delete-field: deleted # 逻辑删除字段其中id-type配置让我栽过跟头。当使用MySQL自增主键时设为auto没问题,但如果用UUID或雪花算法,必须改为assign_id。曾经有次线上事故就是因为这个配置错误导致主键冲突。
3. 核心功能实战演示
3.1 实体类与Mapper的魔法
MP最惊艳的功能莫过于通用Mapper。定义一个User实体类:
@Data @TableName("sys_user") // 实际表名映射 public class User { @TableId(type = IdType.AUTO) private Long id; private String username; @TableField("real_name") // 字段映射 private String realName; @TableLogic // 逻辑删除标记 private Integer deleted; }对应的Mapper接口只需要简单继承:
public interface UserMapper extends BaseMapper<User> { // 无需任何方法定义 }这时你已经拥有了全套CRUD方法。我常用的几个快捷操作:
insert(user):插入记录selectById(1L):按ID查询updateById(user):按ID更新deleteById(1L):按ID删除
3.2 QueryWrapper条件构造器
动态SQL构造是MP的杀手级功能。比如要查询姓"张"的18岁以上用户:
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like("username", "张") .ge("age", 18) .orderByDesc("create_time"); List<User> users = userMapper.selectList(wrapper);等效于SQL:
SELECT * FROM sys_user WHERE username LIKE '%张%' AND age >= 18 ORDER BY create_time DESC踩坑提醒:字段名写错不会编译报错,只会运行时抛出异常。建议使用Lambda写法:
wrapper.like(User::getUsername, "张")
3.3 分页查询的正确姿势
MP的分页需要先配置拦截器:
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }查询示例:
Page<User> page = new Page<>(1, 10); // 当前页, 每页条数 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("department", "研发部"); Page<User> result = userMapper.selectPage(page, wrapper);注意:MySQL分页在大数据量时存在性能问题,我曾处理过一个500万数据表的优化案例,最终采用last_id分页模式替代传统limit。
4. 高级特性与性能优化
4.1 逻辑删除实现
只需要在实体字段加@TableLogic注解,MP会自动处理删除操作:
@TableLogic private Integer deleted;执行deleteById()实际会生成:
UPDATE sys_user SET deleted=1 WHERE id=?查询时自动附加deleted=0条件。这个功能虽然方便,但在关联查询时需要特别注意,我有次就因忽略这个机制导致数据不一致。
4.2 自动填充功能
比如自动填充创建时间:
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;需要实现MetaObjectHandler:
@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); } }4.3 性能优化建议
- 避免N+1查询:使用
@TableField(exist = false)标注非表字段,通过@Select注解或XML处理关联查询 - 批量操作:使用
saveBatch()替代循环单条insert,我测试过万级数据插入,批量方式快8-10倍 - 索引提示:在Wrapper中使用
hint("idx_name")强制使用特定索引
5. 生产环境踩坑实录
5.1 字段映射的坑
有次上线后发现查询结果全是null,排查半天才发现是同事把@TableField("user_name")写成了@TableField("username"),而数据库实际字段是user_name。现在团队强制要求所有字段显式声明映射。
5.2 版本升级的坑
从3.4.x升级到3.5.x时,Wrapper的or()方法行为发生了变化。原先的:
wrapper.eq("A",1).or().eq("B",2)生成的SQL从(A=1) OR (B=2)变成了A=1 OR B=2,导致优先级变化。现在我们都改用Lambda写法:
wrapper.and(w -> w.eq("A",1).or().eq("B",2))5.3 逻辑删除的联表查询
当主表逻辑删除后,如果使用leftJoin关联查询,需要手动添加主表的deleted=0条件,否则会出现"幽灵数据"。这是我们用AOP统一处理的典型场景。
6. 最佳实践总结
经过5个大型项目实践,我们团队沉淀出以下规范:
目录结构:
├── entity ├── mapper │ └── custom // 自定义SQL ├── service │ ├── impl │ └── wrapper // 查询条件封装编码规范:
- 所有查询必须通过Wrapper构建
- 禁止在业务层直接拼接SQL片段
- 分页参数必须校验最大值
扩展建议:
- 自定义通用方法:通过
CustomerSqlInjector扩展 - 多租户实现:使用
TenantLineInnerInterceptor - 数据权限:结合
@DataScope注解处理
- 自定义通用方法:通过
最后分享一个性能检测技巧:在测试环境开启mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl,配合日志级别DEBUG,可以清晰看到每次操作的执行时间和SQL。