【MyBatis-Plus】核心开发指南:高效CRUD与进阶实践
目录
1.前言
插播一条消息~
2.正文
2.1介绍
2.2基础操作
2.2.1准备工作
2.2.2编码
2.2.3CRUD测试
2.3复杂操作
2.3.1常见注解
2.3.1.1@TableName
2.3.1.2@TableField
2.3.1.3@TableId
2.3.2打印日志
2.3.3条件构造器
2.3.3.1QueryWrapper
2.3.3.2UpdateWrapper
2.3.3.3LambdaQueryWrapper
2.3.3.4LambdaUpdateWrapper
2.3.4自定义SQL
2.3.4.1注解方式
2.3.4.2XML映射
核心技巧:Wrapper条件集成
2.3.4.3常见故障
3.小结
1.前言
在传统MyBatis开发中,我们常陷入重复劳动:
- 每张表需手动编写基础CRUD的Mapper接口与XML
- 复杂查询需拼接SQL字符串,易引发语法错误与SQL注入风险
- 分页、乐观锁等通用功能需重复造轮子
MyBatis-Plus(MP)的诞生直击上述痛点。作为MyBatis的增强工具包,它在保留MyBatis所有灵活性的基础上,通过两大核心设计大幅提升开发效率:
- 内置通用Mapper/Service:自动化单表CRUD操作,减少70%重复代码
- 链式条件构造器:用类型安全的Java API替代SQL字符串拼接
- 插件化架构:分页、性能分析、乐观锁等企业级功能开箱即用
本文将系统解析MP的核心工作机制,涵盖以下重点:
- 基础CRUD的零SQL实现
- 注解驱动的表字段映射策略
- 基于Lambda的条件构造器安全写法
- 自定义SQL与MP特性的无缝融合
插播一条消息~
🔍十年经验淬炼 · 系统化AI学习平台推荐
系统化学习AI平台https://www.captainbed.cn/scy/
📚 完整知识体系:从数学基础 → 工业级项目(人脸识别/自动驾驶/GANs),内容由浅入深
💻 实战为王:每小节配套可运行代码案例(提供完整源码)
🎯 零基础友好:用生活案例讲解算法,无需担心数学/编程基础
🚀 特别适合
- 想系统补强AI知识的开发者
- 转型人工智能领域的从业者
- 需要项目经验的学生
2.正文
2.1介绍
什么是MyBatis Plus呢?
简介 | MyBatis-Plushttps://baomidou.com/introduce/
一句话:在MyBatis基础上只做增强不做改变,简化开发的“效率神器”。
它保留了MyBatis的全部特性,同时内置了CRUD接口、分页插件、代码生成器等实用功能,让你告别重复写SQL的痛苦。
2.2基础操作
2.2.1准备工作
核心依赖配置:
<!-- Spring Boot Starter -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency><!-- MySQL驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version>
</dependency>
配置文件(application.yml):
spring:datasource:url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=UTCusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivermybatis-plus:configuration:# 关键:开启SQL日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:# 全局表前缀table-prefix: t_
实体类定义:
@Data
@TableName("user") // 指定对应数据库表
public class User {@TableId(type = IdType.AUTO) // 主键自增策略private Long id;private String name;private Integer age;@TableField("user_email") // 映射字段名private String email;
}
Mapper接口定义:
@Mapper
public interface UserMapper extends BaseMapper<User> {// 无需任何方法声明// 已自动继承17个基础CRUD方法
}
2.2.2编码
核心CRUD方法:
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;// 插入public int createUser(User user) {return userMapper.insert(user);// 注意:自增ID会自动回填到user对象}// 按ID查询public User getUserById(Long id) {return userMapper.selectById(id);}// 更新public int updateUser(User user) {return userMapper.updateById(user);}// 删除public int deleteUser(Long id) {return userMapper.deleteById(id);}// 查询所有public List<User> getAllUsers() {return userMapper.selectList(null); // null表示无条件}
}
2.2.3CRUD测试
测试场景设计:
@SpringBootTest
class UserCrudTests {@Autowiredprivate UserService userService;@Testvoid testFullCrudFlow() {// 1. 创建测试数据User newUser = new User();newUser.setName("TestUser");newUser.setAge(30);newUser.setEmail("test@mp.com");// 2. 插入测试(验证日志输出)int createResult = userService.createUser(newUser);assertEquals(1, createResult); // 影响行数=1assertNotNull(newUser.getId()); // 自增ID回填验证// 3. 查询验证User dbUser = userService.getUserById(newUser.getId());assertEquals("TestUser", dbUser.getName());// 4. 更新测试dbUser.setEmail("updated@mp.com");int updateResult = userService.updateUser(dbUser);assertEquals(1, updateResult);// 5. 二次查询验证更新User updatedUser = userService.getUserById(dbUser.getId());assertEquals("updated@mp.com", updatedUser.getEmail());// 6. 删除测试int deleteResult = userService.deleteUser(updatedUser.getId());assertEquals(1, deleteResult);// 7. 验证删除结果User deletedUser = userService.getUserById(updatedUser.getId());assertNull(deletedUser);}
}
关键日志分析(控制台输出):
技术要点总结:
零SQL实现:所有基础CRUD操作无需编写SQL语句
自动映射:
表名映射:
@TableName
或全局配置字段映射:自动驼峰转下划线(
userEmail → user_email
)主键策略:通过
@TableId
指定(自增/雪花算法等)方法继承:
// BaseMapper提供的基础方法 int insert(T entity); int deleteById(Serializable id); int updateById(T entity); T selectById(Serializable id); List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
事务支持:默认继承Spring事务管理
自动填充:插入/更新时自动填充
create_time
等字段(需配合@TableField
)
2.3复杂操作
2.3.1常见注解
2.3.1.1@TableName
作用:建立实体类与数据库表的映射关系
使用场景:
表名与类名不一致时(如类名
User
对应表名sys_user
)需要动态表名(如分表场景)
需要指定数据库schema
核心参数:
@TableName(value = "sys_user", // 实际表名schema = "enterprise_db", // 数据库schemakeepGlobalPrefix = true // 是否使用全局表前缀
)
public class User {// ...
}
配置示例:
# application.yml
mybatis-plus:global-config:db-config:table-prefix: "tbl_" # 全局表前缀table-underline: true # 开启下划线命名转换
使用规则:
优先使用注解中的
value
值未配置
value
时,自动转换类名(User → user)开启
keepGlobalPrefix
时,表名 = 全局前缀 + value值
2.3.1.2@TableField
作用:建立属性与表字段的映射关系
核心参数:
public class User {@TableField(value = "full_name", // 数据库字段名exist = true, // 是否为表字段(false表示虚拟字段)insertStrategy = FieldStrategy.NOT_NULL, // 插入策略updateStrategy = FieldStrategy.IGNORED, // 更新策略fill = FieldFill.INSERT_UPDATE // 自动填充策略)private String name;
}
字段策略(FieldStrategy):
策略 | 说明 | 使用场景 |
---|---|---|
IGNORED | 忽略空值判断 | 强制更新字段(包括null值) |
NOT_NULL | 非NULL才更新/作为条件 | 默认策略 |
NOT_EMPTY | 非空才更新(对字符串有效) | 避免空字符串覆盖 |
NEVER | 从不更新 | 只读字段 |
自动填充(FieldFill):
// 自动填充处理器
@Component
public class AutoFillHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());}@Overridepublic void updateFill(MetaObject metaObject) {this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());}
}// 实体类配置
public class User {@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;
}
2.3.1.3@TableId
作用:标识主键字段并指定主键策略
核心参数:
public class User {@TableId(value = "user_id", // 主键字段名type = IdType.ASSIGN_ID // 主键生成策略)private Long id;
}
主键策略(IdType):
策略 | 说明 | 适用场景 |
---|---|---|
AUTO | 数据库自增 | MySQL、PostgreSQL |
INPUT | 用户手动输入 | 业务主键 |
ASSIGN_ID | 分配ID(雪花算法) | 分布式系统(默认策略) |
ASSIGN_UUID | 分配UUID | 需要字符串主键 |
NONE | 无状态 | 需手动设置 |
雪花算法配置:
mybatis-plus:global-config:db-config:id-type: assign_idworker-id: 1 # 工作机器ID(0-31)datacenter-id: 1 # 数据中心ID(0-31)
注解应用综合示例
@TableName(value = "sys_users", schema = "hr_db")
public class User {@TableId(type = IdType.ASSIGN_ID)private Long id;@TableField(value = "full_name", updateStrategy = FieldStrategy.NOT_EMPTY)private String name;@TableField(exist = false) // 非数据库字段private String tempToken;@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(value = "is_deleted",insertStrategy = FieldStrategy.NEVER, // 插入时不包含updateStrategy = FieldStrategy.NEVER // 更新时不包含)private Boolean deleted;
}
关键提示:
使用
@TableField(exist=false)
标记DTO扩展字段敏感字段设置
updateStrategy=FieldStrategy.NEVER
防止意外更新主键策略必须与实际数据库结构匹配(自增字段需配置
AUTO
)开启SQL日志验证注解是否生效
2.3.2打印日志
核心价值:
- SQL审计 → 验证自动生成的SQL是否符合预期
- 性能优化 → 识别慢查询和N+1问题
- 故障排查 → 定位参数绑定错误和语法异常
配置方式
基础配置(application.yml):
mybatis-plus:configuration:# 标准输出(控制台)log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# SLF4J+Logback(生产推荐)# log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
日志级别控制:
# Logback配置(src/main/resources/logback.xml)
<logger name="com.baomidou.mybatisplus" level="DEBUG"/>
<logger name="org.apache.ibatis" level="TRACE"/> # 查看参数细节
日志格式解析
典型日志输出:
字段说明:
日志行 | 含义 | 技术细节 |
---|---|---|
Preparing: | 待执行SQL | 显示预编译语句(?占位符) |
Parameters: | 绑定参数 | 按顺序显示参数值和类型 |
Total: | 查询结果行数 | 仅select语句显示 |
Updates: | DML操作影响行数 | insert/update/delete操作 |
Time: 23ms | 执行耗时(需额外配置) | 性能分析关键指标 |
日志级别策略:
环境 | 推荐配置 | 说明 |
---|---|---|
开发环境 | StdOutImpl + TRACE级别 | 实时查看完整SQL |
测试环境 | Slf4jImpl + DEBUG级别 | 记录所有SQL但不输出参数值 |
生产环境 | Slf4jImpl + WARN级别 | 仅记录慢查询和异常SQL |
2.3.3条件构造器
核心架构
// 条件构造器类继承体系
AbstractWrapper├── QueryWrapper // 查询条件构造├── UpdateWrapper // 更新条件构造├── LambdaQueryWrapper // Lambda安全查询└── LambdaUpdateWrapper // Lambda安全更新
2.3.3.1QueryWrapper
核心功能:构建SELECT查询条件
适用场景:复杂条件查询、排序、分组
基础用法:
QueryWrapper<User> query = new QueryWrapper<>();
query.select("id", "name", "age") // 指定查询列.like("name", "张") // 模糊查询.gt("age", 20) // 大于.between("create_time", startDate, endDate) // 范围查询.orderByDesc("id") // 排序.last("LIMIT 10"); // 自定义SQL片段List<User> users = userMapper.selectList(query);
链式条件方法:
方法 | SQL等价 | 说明 |
---|---|---|
eq("column", val) | column = val | 等于 |
ne("column", val) | column <> val | 不等于 |
gt("column", val) | column > val | 大于 |
ge("column", val) | column >= val | 大于等于 |
lt("column", val) | column < val | 小于 |
le("column", val) | column <= val | 小于等于 |
between(c, v1, v2) | c BETWEEN v1 AND v2 | 范围查询 |
like("c", "val%") | c LIKE 'val%' | 模糊查询 |
in("c", list) | c IN (v1,v2) | IN查询 |
isNull("c") | c IS NULL | 空值判断 |
groupBy("c1,c2") | GROUP BY c1,c2 | 分组 |
having("sum(a)>0") | HAVING sum(a)>0 | 分组后条件 |
2.3.3.2UpdateWrapper
核心功能:构建UPDATE操作条件
适用场景:条件更新、字段增量操作
基础用法:
UpdateWrapper<User> update = new UpdateWrapper<>();
update.set("email", "new@email.com") // 设置更新值.setSql("balance = balance + 100") // 原生SQL更新.eq("status", 1) // 更新条件.gt("last_login", LocalDate.now().minusMonths(3)); // 三个月内有登录int rows = userMapper.update(null, update); // entity传null
特殊操作符:
// 数学运算
update.setSql("view_count = view_count + 1");// JSON字段更新 (MySQL 5.7+)
update.setSql("profile = JSON_SET(profile, '$.level', 2)");// CASE WHEN更新
update.setSql("vip_level = CASE WHEN score>1000 THEN 2 ELSE 1 END");
2.3.3.3LambdaQueryWrapper
核心价值:消除字段名字符串硬编码
编译时检查:字段引用通过方法引用实现
基础用法:
LambdaQueryWrapper<User> lambdaQuery = new LambdaQueryWrapper<>();
lambdaQuery.select(User::getId, User::getName).like(User::getName, "王").between(User::getAge, 20, 30).orderByDesc(User::getCreateTime);List<User> users = userMapper.selectList(lambdaQuery);
复杂嵌套查询:
lambdaQuery.and(wrapper -> wrapper.gt(User::getScore, 90).or().eq(User::getVipLevel, 2)).nested(wrapper -> wrapper.lt(User::getAge, 18).or().gt(User::getAge, 60));
等价SQL:
SELECT id, name
FROM user
WHERE (score > 90 OR vip_level = 2)AND (age < 18 OR age > 60)
2.3.3.4LambdaUpdateWrapper
核心价值:类型安全的更新操作
基础用法:
LambdaUpdateWrapper<User> lambdaUpdate = new LambdaUpdateWrapper<>();
lambdaUpdate.set(User::getEmail, "updated@email.com").set(User::getUpdateTime, LocalDateTime.now()).eq(User::getDepartmentId, 101).in(User::getRole, Arrays.asList("admin", "editor"));int rows = userMapper.update(null, lambdaUpdate);
条件更新技巧:
// 按条件设置不同值
lambdaUpdate.setSql("score = CASE " +"WHEN age < 30 THEN score + 10 " +"WHEN age > 50 THEN score + 5 " +"ELSE score + 8 END").le(User::getScore, 100);
四类构造器对比矩阵
特性 | QueryWrapper | UpdateWrapper | LambdaQueryWrapper | LambdaUpdateWrapper |
---|---|---|---|---|
核心用途 | SELECT条件 | UPDATE条件 | 类型安全SELECT | 类型安全UPDATE |
字段引用方式 | 字符串列名 | 字符串列名 | 方法引用(::) | 方法引用(::) |
编译时检查 | ❌ | ❌ | ✅ | ✅ |
重构友好度 | 低 | 低 | 高 | 高 |
复杂嵌套支持 | ✅ | ✅ | ✅ | ✅ |
SQL函数支持 | ✅ | ✅ | ✅ | ✅ |
推荐指数 | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
2.3.4自定义SQL
解决MP的局限性场景:
- 多表关联查询(JOIN操作)
- 复杂聚合函数(SUM/COUNT嵌套)
- 数据库特性函数(如Oracle的CONNECT BY)
- 存储过程/函数调用
- 特殊分页需求(优化count查询)
实现方式对比
方式 | 适用场景 | 优势 | 劣势 |
---|---|---|---|
注解SQL | 简单动态SQL | 零配置快速实现 | 复杂SQL可读性差 |
XML映射 | 复杂SQL/多表操作 | 完整SQL语法支持 | 需额外配置文件 |
Wrapper+ | 条件复用场景 | 动态条件与固定SQL结合 | 仅限WHERE/ORDER BY |
2.3.4.1注解方式
基础语法:
@Select("SELECT * FROM user WHERE age > #{minAge}")
List<User> findAdultUsers(@Param("minAge") int minAge);
动态条件注解:
@Select("<script>" +"SELECT * FROM user " +"WHERE 1=1 " +"<if test='name != null'> AND name LIKE #{name} </if>" +"<if test='minAge != null'> AND age >= #{minAge} </if>" +"</script>")
List<User> findUsersDynamic(@Param("name") String name, @Param("minAge") Integer minAge
);
多表关联示例:
@Select("SELECT u.*, d.dept_name " +"FROM user u " +"LEFT JOIN department d ON u.dept_id = d.id " +"WHERE u.status = 1")
List<Map<String, Object>> findUsersWithDept();
限制:
不支持
${ew.customSqlSegment}
注入复杂SQL可维护性差
2.3.4.2XML映射
基础配置:
<!-- 启用XML扫描 -->
mybatis-plus:mapper-locations: classpath*:mapper/**/*.xml
核心技巧:Wrapper条件集成
Mapper接口定义:
public interface UserMapper extends BaseMapper<User> {// 使用@Param("ew")固定命名List<UserDTO> selectComplexUsers(@Param("ew") Wrapper<User> wrapper);
}
XML映射实现:
<select id="selectComplexUsers" resultType="com.example.dto.UserDTO">SELECT u.id, u.name,d.dept_name AS deptName,COUNT(o.id) AS orderCountFROM user uLEFT JOIN department d ON u.dept_id = d.idLEFT JOIN orders o ON o.user_id = u.id<!-- 关键:集成Wrapper生成的条件 -->${ew.customSqlSegment}GROUP BY u.id
</select>
Java调用示例:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张").gt(User::getAge, 25).orderByDesc(User::getCreateTime);List<UserDTO> users = userMapper.selectComplexUsers(wrapper);
生成SQL:
SELECT u.id, u.name, d.dept_name, COUNT(o.id)
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
LEFT JOIN orders o ON o.user_id = u.id
WHERE name LIKE '%张%' AND age > 25 -- 自动生成的条件
GROUP BY u.id
ORDER BY create_time DESC -- 自动排序
2.3.4.3常见故障
问题现象 | 原因分析 | 解决方案 |
---|---|---|
ew.customSqlSegment 无效 | 未使用@Param("ew") | 检查Mapper参数命名 |
分页总数错误 | 未重写count查询 | 实现selectPageCount 方法 |
字段映射失败 | 缺少@ResultMap 配置 | 显式定义resultMap |
动态SQL解析异常 | XML标签未闭合 | 检查<script> 完整性 |
存储过程调用失败 | 未设置statementType | 添加statementType="CALLABLE" |
通过合理选择自定义SQL方案,可在保持MP高效开发的同时,应对各类复杂数据库操作场景。
3.小结
通过本文的系统讲解,我们完整梳理了MyBatis Plus的核心功能体系。从环境搭建到复杂查询,从自动映射机制到条件构造器的深度应用,每个技术点都提供了可落地的解决方案。
- 开发效率提升:通用Mapper消灭单表CRUD代码
- 维护成本降低:Lambda Wrapper杜绝SQL拼接错误
- 架构统一性:注解体系提供标准化映射方案
特性 | 最佳实践 | 避坑指南 |
---|---|---|
基础CRUD | 优先继承BaseMapper | 主键策略需显式声明(@TableId ) |
字段映射 | 非常规字段必用@TableField | 非表字段标记exist=false |
条件构造器 | 强制使用Lambda Wrapper | 避免字段名字符串硬编码 |
自定义SQL | 结合${ew.customSqlSegment} | XML中防SQL注入过滤 |
开发建议:MP适用于80%的单表操作场景,剩余20%复杂查询通过
自定义SQL+Wrapper
解决。掌握此平衡点,可最大化开发效能。
建议开发者在实际项目中逐步引入MyBatis Plus的特性,从基础CRUD开始,逐步过渡到复杂查询和分页处理。通过本文提供的代码示例模板,可以快速构建标准的数据库访问层,最终实现"代码精简、开发效率提升"的优化目标。