1. 为什么选择MyBatis-Plus
作为Java开发者,我们或多或少都接触过MyBatis这个经典的ORM框架。但原生MyBatis需要编写大量XML映射文件和重复的CRUD代码,这在实际开发中相当耗时。MyBatis-Plus(简称MP)就是在这样的背景下应运而生的增强工具包。
我最初接触MP是在2018年一个电商项目中,当时团队正在为大量重复的DAO层代码发愁。引入MP后,开发效率提升了近40%,特别是单表操作几乎不用再写SQL。现在MP已经成为国内Java开发的事实标准之一,最新3.5.x版本更是强化了与Spring Boot的深度整合。
注意:MyBatis-Plus不是替代MyBatis,而是在其基础上进行扩展,所有MyBatis的功能和特性在MP中都能正常使用
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与项目搭建
2.1 版本匹配建议
根据网络热词搜索数据,目前最受关注的是MP 3.5.17版本。经过实测,它与Spring Boot版本的对应关系如下:
| MyBatis-Plus版本 | 推荐Spring Boot版本 | 特点说明 |
|---|---|---|
| 3.5.17 | 2.7.x | 最稳定组合 |
| 3.5.x | 2.6.x-2.7.x | 兼容性良好 |
| 3.4.x | 2.5.x-2.6.x | 旧项目维护 |
建议新手直接使用Spring Initializr创建项目,勾选:
- Spring Web
- MySQL Driver
- Lombok(简化实体类)
2.2 依赖配置详解
在pom.xml中添加核心依赖(以3.5.17为例):
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.17</version>
</dependency>
这里有几个关键点需要注意:
- 使用
boot-starter而非核心包,它会自动配置Spring环境集成 - 不需要额外引入MyBatis依赖,starter已包含
- 建议锁定版本号,避免自动升级带来意外问题
3. 基础CRUD实战
3.1 实体类设计规范
以用户表为例,标准的MP实体类应该这样写:
java复制@Data
@TableName("sys_user") // 显式指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键策略
private Long id;
@TableField("username") // 字段映射
private String name;
private Integer age;
@TableField(exist = false) // 非表字段
private String tempData;
}
踩坑经验:
- 一定要加
@TableName注解,否则MP默认用类名转下划线作为表名 - 字段名与数据库列名不一致时,必须用
@TableField显式指定 - 使用Lombok的
@Data可以大幅减少getter/setter代码
3.2 Mapper接口的魔法
MP的Mapper接口只需要简单继承BaseMapper:
java复制public interface UserMapper extends BaseMapper<User> {
// 无需任何方法定义
}
这个空接口已经自动具备:
- insert/delete/update/select等基础CRUD
- 带条件的批量操作
- 分页查询能力(需配合分页插件)
实测中发现的技巧:
- 可以在接口中添加自定义SQL方法,MP会自动识别XML配置
- 复杂查询建议使用MP的Wrapper构建条件
3.3 服务层最佳实践
典型的Service实现:
java复制@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
public List<User> findAdults() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getAge, 18); // age >= 18
return this.list(wrapper);
}
}
为什么推荐这种写法:
- 继承
ServiceImpl获得所有MP增强方法 - 使用Lambda表达式避免硬编码字段名
- 业务方法可以组合多个基础操作
4. 分页查询深度解析
4.1 配置分页插件
这是大多数新手容易遗漏的关键步骤:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
常见配置问题:
- 忘记配置导致分页失效
- 多个数据源时需要特殊处理
- 建议添加性能监控插件(如
PerformanceInterceptor)
4.2 分页查询实战
标准的分页查询示例:
java复制Page<User> page = new Page<>(1, 10); // 当前页, 每页条数
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(User::getAge);
Page<User> result = userService.page(page, wrapper);
System.out.println("总记录数:" + result.getTotal());
result.getRecords().forEach(System.out::println);
分页原理揭秘:
- MP会自动将Page对象转换为limit语句
- 先执行COUNT查询获取总数
- 再执行分页数据查询
- 对主流数据库(MySQL、Oracle等)有方言适配
5. 高级特性与性能优化
5.1 自动填充功能
对于create_time、update_time等字段,可以完全交给MP处理:
java复制@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
需要配置元对象处理器:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
5.2 逻辑删除实现
零代码实现逻辑删除:
- 数据库添加
deleted字段(默认0) - 实体类添加注解:
java复制@TableLogic
private Integer deleted;
MP会自动:
- 删除操作变为update语句
- 查询自动过滤已删除数据
- 可通过wrapper忽略删除状态
5.3 性能优化建议
经过多个项目验证的有效方案:
- 批处理操作:
java复制// 批量插入(实测比单条循环快10倍以上)
userService.saveBatch(userList, 1000); // 每批1000条
- 禁用MP的SQL打印(生产环境):
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境
# log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl # 生产环境
- 合理使用索引提示:
java复制wrapper.last("FORCE INDEX (idx_username)");
6. 常见问题排查指南
6.1 映射异常处理
当遇到"Unknown column"错误时:
- 检查
@TableField注解的value是否正确 - 确认数据库字段命名风格(MP默认是下划线转驼峰)
- 使用
@TableName(autoResultMap = true)解决复杂映射
6.2 事务失效场景
MP与Spring事务整合时的坑:
- 直接调用Mapper方法不会走事务
- 解决方案:
java复制@Transactional
public void batchUpdate(List<User> users) {
// 必须通过Service方法调用
this.updateBatchById(users);
}
6.3 多数据源配置
推荐使用dynamic-datasource-spring-boot-starter:
yaml复制spring:
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/main
username: root
password: 123456
slave:
url: jdbc:mysql://localhost:3307/replica
username: root
password: 123456
在Mapper上添加注解:
java复制@DS("slave") // 指定数据源
public interface UserMapper extends BaseMapper<User> {
}
7. 项目实战建议
经过多个项目验证的架构方案:
-
分层规范:
- Controller:参数校验、结果包装
- Service:业务逻辑、事务控制
- Mapper:仅做数据库操作
-
通用返回体设计:
java复制public class R<T> {
private Integer code;
private String msg;
private T data;
public static <T> R<T> ok(T data) {
R<T> r = new R<>();
r.setCode(200);
r.setData(data);
return r;
}
}
- 参数校验最佳实践:
java复制@PostMapping("/save")
public R<String> saveUser(@Valid @RequestBody UserDTO dto) {
// 自动校验DTO中的@NotBlank等注解
return R.ok("操作成功");
}
在微服务环境下,建议将MP的配置提取到公共模块,避免每个服务重复配置。对于复杂查询场景,可以结合QueryDSL使用,获得更好的类型安全性和可读性
