MyBatis-Plus详解
本章导读
MyBatis-Plus 是 MyBatis 的增强工具,在保持 MyBatis 灵活性的同时,提供了强大的 CRUD 操作、条件构造器、代码生成器等功能。本章系统讲解 MyBatis-Plus 的核心特性,从基础配置到高级用法,帮助你大幅提升数据访问层的开发效率。
学习目标:
- 目标1:掌握 MyBatis-Plus 的核心注解和 BaseMapper 的使用方法
- 目标2:熟练使用 Lambda 条件构造器实现复杂查询
- 目标3:理解分页插件、乐观锁、逻辑删除等高级特性并正确应用
前置知识:MyBatis 核心原理、Lambda 表达式
阅读时长:约 50 分钟
一、知识概述
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。它提供了通用的 CRUD 操作、强大的条件构造器、代码生成器、分页插件等功能,大大简化了数据访问层的开发工作。
本文将深入讲解 MyBatis-Plus 的核心功能、原理和最佳实践,帮助你快速掌握这个强大的 ORM 增强框架。
二、快速入门
2.1 引入依赖
<!-- Maven 依赖 -->
<dependencies>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Lombok(可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2.2 配置文件
# application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# MyBatis-Plus 配置
mybatis-plus:
# Mapper XML 文件位置
mapper-locations: classpath*:/mapper/**/*.xml
# 实体类扫描路径
type-aliases-package: com.example.entity
# 配置
configuration:
# 开启驼峰命名转换
map-underscore-to-camel-case: true
# 日志输出
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# 全局配置
global-config:
db-config:
# 主键策略
id-type: auto
# 逻辑删除字段
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
2.3 快速使用
/**
* MyBatis-Plus 快速入门示例
*/
@SpringBootApplication
@MapperScan("com.example.mapper")
public class MyBatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisPlusApplication.class, args);
}
}
/**
* 实体类
*/
@Data
@TableName("sys_user") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
@TableField("username") // 指定字段名
private String username;
private String email;
private Integer age;
private Integer status;
@TableField(fill = FieldFill.INSERT) // 自动填充
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic // 逻辑删除
@TableField("is_deleted")
private Integer deleted;
@Version // 乐观锁
private Integer version;
@TableField(exist = false) // 非数据库字段
private String extra;
}
/**
* Mapper 接口 - 继承 BaseMapper 即可获得 CRUD 能力
*/
public interface UserMapper extends BaseMapper<User> {
// BaseMapper 已提供:
// - insert(T entity)
// - deleteById(Serializable id)
// - updateById(T entity)
// - selectById(Serializable id)
// - selectList(Wrapper<T> wrapper)
// - selectPage(Page<T> page, Wrapper<T> wrapper)
// ... 等十余个通用方法
}
/**
* Service 接口 - 继承 IService
*/
public interface UserService extends IService<User> {
// IService 提供了更丰富的方法
}
/**
* Service 实现
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
// 无需手动编写 CRUD 方法
}
/**
* 测试使用
*/
@SpringBootTest
public class QuickStartTest {
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
@Test
public void testInsert() {
User user = new User();
user.setUsername("zhangsan");
user.setEmail("zhangsan@example.com");
user.setAge(25);
// 插入
int rows = userMapper.insert(user);
System.out.println("插入行数: " + rows);
System.out.println("自增ID: " + user.getId());
}
@Test
public void testSelect() {
// 根据 ID 查询
User user = userMapper.selectById(1L);
System.out.println(user);
// 查询所有
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
// 条件查询
List<User> list = userMapper.selectList(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.like(User::getUsername, "张")
.orderByDesc(User::getCreateTime)
);
}
@Test
public void testUpdate() {
User user = new User();
user.setId(1L);
user.setAge(26);
// 根据 ID 更新(只更新非空字段)
int rows = userMapper.updateById(user);
System.out.println("更新行数: " + rows);
}
@Test
public void testDelete() {
// 根据 ID 删除(逻辑删除)
int rows = userMapper.deleteById(1L);
System.out.println("删除行数: " + rows);
}
}
三、核心注解详解
3.1 @TableName - 表名注解
/**
* @TableName - 指定实体对应的表名
*/
@Data
// 方式1:直接指定表名
@TableName("sys_user")
public class User1 {
private Long id;
private String username;
}
// 方式2:使用变量(支持动态表名)
@TableName("${prefix}_user")
public class User2 {
private Long id;
private String username;
}
// 方式3:指定 schema
@TableName(value = "user", schema = "mydb")
public class User3 {
private Long id;
private String username;
}
// 方式4:设置自动映射
@TableName(value = "sys_user", autoResultMap = true)
public class User4 {
private Long id;
private String username;
// autoResultMap = true 时,可以使用 TypeHandler
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> extra;
}
/**
* 动态表名处理器
*/
@Component
public class DynamicTableNameHandler implements TableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
// 根据上下文动态设置表名
String suffix = TenantContext.getTenantSuffix();
if (StringUtils.hasText(suffix)) {
return tableName + "_" + suffix;
}
return tableName;
}
}
// 配置动态表名
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicInterceptor =
new DynamicTableNameInnerInterceptor();
dynamicInterceptor.setTableNameHandler((sql, tableName) -> {
// 动态表名逻辑
if ("sys_user".equals(tableName)) {
return "sys_user_" + TenantContext.getTenantId();
}
return tableName;
});
interceptor.addInnerInterceptor(dynamicInterceptor);
return interceptor;
}
}
3.2 @TableId - 主键注解
/**
* @TableId - 主键策略
*/
public class TableIdExamples {
/**
* IdType 枚举值
*/
public enum IdType {
AUTO, // 数据库自增
NONE, // 无状态(默认)
INPUT, // 用户输入
ASSIGN_ID, // 雪花算法生成 Long 类型 ID
ASSIGN_UUID // UUID
}
@Data
@TableName("user")
public static class UserAuto {
@TableId(type = IdType.AUTO) // 数据库自增
private Long id;
}
@Data
@TableName("user")
public static class UserAssignId {
@TableId(type = IdType.ASSIGN_ID) // 雪花算法(默认策略)
private Long id;
}
@Data
@TableName("user")
public static class UserAssignUUID {
@TableId(type = IdType.ASSIGN_UUID) // UUID(去掉横线)
private String id;
}
@Data
@TableName("user")
public static class UserInput {
@TableId(type = IdType.INPUT) // 用户输入(需要手动设置 ID)
private Long id;
}
}
/**
* 自定义 ID 生成器
*/
@Component
public class CustomIdGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
// 自定义 ID 生成逻辑
// 可以使用雪花算法、数据库序列等
return SnowflakeIdWorker.nextId();
}
@Override
public String nextUUID(Object entity) {
// 自定义 UUID 生成
return UUID.randomUUID().toString().replace("-", "");
}
}
3.3 @TableField - 字段注解
/**
* @TableField - 字段映射
*/
public class TableFieldExamples {
@Data
@TableName("sys_user")
public static class User {
@TableId
private Long id;
// 指定数据库字段名
@TableField("user_name")
private String username;
// 非数据库字段
@TableField(exist = false)
private String extraField;
// 自动填充 - 插入时填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
// 自动填充 - 插入和更新时填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
// 自动填充 - 更新时填充
@TableField(fill = FieldFill.UPDATE)
private Date modifyTime;
// 条件更新策略
@TableField(updateStrategy = FieldStrategy.NOT_EMPTY)
private String email;
// 条件查询策略
@TableField(whereStrategy = FieldStrategy.NOT_NULL)
private Integer status;
// 使用 TypeHandler
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> config;
// 指定 JDBC 类型
@TableField(jdbcType = JdbcType.VARCHAR)
private String description;
// 指定数值精度
@TableField(numericScale = 2)
private BigDecimal amount;
}
/**
* 字段策略
*/
public enum FieldStrategy {
NOT_NULL, // 非 NULL 判断
NOT_EMPTY, // 非空判断(仅针对字符串类型,排除空串)
DEFAULT, // 默认(跟随全局配置)
IGNORED, // 忽略判断
NEVER // 从不加入 SQL
}
/**
* 自动填充处理器
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 插入时自动填充
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
this.strictInsertFill(metaObject, "createBy", String.class,
getCurrentUserId());
this.strictInsertFill(metaObject, "version", Integer.class, 1);
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时自动填充
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
this.strictUpdateFill(metaObject, "updateBy", String.class,
getCurrentUserId());
}
private String getCurrentUserId() {
return SecurityContextHolder.getContext()
.getAuthentication().getName();
}
}
}
3.4 其他核心注解
/**
* 其他常用注解
*/
public class OtherAnnotations {
/**
* @Version - 乐观锁
*/
@Data
@TableName("product")
public static class Product {
@TableId
private Long id;
private String name;
private Integer stock;
@Version // 乐观锁版本号
private Integer version;
}
/**
* @TableLogic - 逻辑删除
*/
@Data
@TableName("user")
public static class User {
@TableId
private Long id;
private String username;
@TableLogic
private Integer deleted; // 0-未删除,1-已删除
}
// 可以全局配置
// mybatis-plus.global-config.db-config.logic-delete-field=deleted
// mybatis-plus.global-config.db-config.logic-delete-value=1
// mybatis-plus.global-config.db-config.logic-not-delete-value=0
/**
* @KeySequence - 序列主键(Oracle)
*/
@Data
@KeySequence("SEQ_USER") // Oracle 序列名
@TableName("SYS_USER")
public static class OracleUser {
@TableId(type = IdType.INPUT)
private Long id;
private String username;
}
/**
* @InterceptorIgnore - 忽略拦截器
*/
public interface UserMapper extends BaseMapper<User> {
// 忽略租户拦截器
@InterceptorIgnore(tenantLine = "true")
List<User> selectAllIgnoreTenant();
// 忽略数据权限
@InterceptorIgnore(dataPermission = "true")
List<User> selectAllIgnorePermission();
}
}
四、条件构造器
4.1 QueryWrapper 基础
/**
* QueryWrapper - 条件构造器基础用法
*/
@SpringBootTest
public class QueryWrapperTest {
@Autowired
private UserMapper userMapper;
/**
* 基础比较操作
*/
@Test
public void testBasicConditions() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 等于
wrapper.eq("username", "zhangsan");
// SQL: WHERE username = 'zhangsan'
// 不等于
wrapper.ne("status", 0);
// SQL: AND status <> 0
// 大于 / 大于等于
wrapper.gt("age", 18);
wrapper.ge("age", 18);
// SQL: AND age > 18, AND age >= 18
// 小于 / 小于等于
wrapper.lt("age", 60);
wrapper.le("age", 60);
// SQL: AND age < 60, AND age <= 60
// BETWEEN
wrapper.between("age", 20, 30);
// SQL: AND age BETWEEN 20 AND 30
// NOT BETWEEN
wrapper.notBetween("age", 10, 20);
// SQL: AND age NOT BETWEEN 10 AND 20
List<User> users = userMapper.selectList(wrapper);
}
/**
* 模糊查询
*/
@Test
public void testLikeConditions() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// LIKE '%keyword%'
wrapper.like("username", "张");
// SQL: WHERE username LIKE '%张%'
// NOT LIKE '%keyword%'
wrapper.notLike("email", "test");
// SQL: AND email NOT LIKE '%test%'
// LIKE 'keyword%'
wrapper.likeRight("username", "张");
// SQL: AND username LIKE '张%'
// LIKE '%keyword'
wrapper.likeLeft("username", "三");
// SQL: AND username LIKE '%三'
List<User> users = userMapper.selectList(wrapper);
}
/**
* 范围查询
*/
@Test
public void testInConditions() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// IN
wrapper.in("status", 1, 2, 3);
// SQL: WHERE status IN (1, 2, 3)
// IN (集合)
List<Integer> statusList = Arrays.asList(1, 2, 3);
wrapper.in("status", statusList);
// NOT IN
wrapper.notIn("age", 18, 19, 20);
// SQL: AND age NOT IN (18, 19, 20)
// IN SQL (子查询)
wrapper.inSql("dept_id", "SELECT id FROM dept WHERE level = 1");
// SQL: AND dept_id IN (SELECT id FROM dept WHERE level = 1)
// NOT IN SQL
wrapper.notInSql("id", "SELECT user_id FROM blacklist");
List<User> users = userMapper.selectList(wrapper);
}
/**
* NULL 判断
*/
@Test
public void testNullConditions() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// IS NULL
wrapper.isNull("email");
// SQL: WHERE email IS NULL
// IS NOT NULL
wrapper.isNotNull("phone");
// SQL: AND phone IS NOT NULL
List<User> users = userMapper.selectList(wrapper);
}
/**
* 排序
*/
@Test
public void testOrderBy() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// ORDER BY
wrapper.orderByAsc("create_time");
// SQL: ORDER BY create_time ASC
wrapper.orderByDesc("age");
// SQL: ORDER BY age DESC
// 多字段排序
wrapper.orderByAsc("status", "create_time");
// 条件排序
wrapper.orderBy(true, false, "id"); // 升序
wrapper.orderBy(true, true, "id"); // 降序
List<User> users = userMapper.selectList(wrapper);
}
/**
* 分组
*/
@Test
public void testGroupBy() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// GROUP BY
wrapper.select("status", "COUNT(*) as count")
.groupBy("status");
// SQL: SELECT status, COUNT(*) as count FROM user GROUP BY status
// HAVING
wrapper.having("COUNT(*) > {0}", 5);
// SQL: HAVING COUNT(*) > 5
List<Map<String, Object>> result = userMapper.selectMaps(wrapper);
}
}
4.2 LambdaQueryWrapper
/**
* LambdaQueryWrapper - 使用 Lambda 表达式,避免硬编码字段名
*/
@SpringBootTest
public class LambdaQueryWrapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testLambda() {
// 使用 Lambda 避免字段名硬编码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 基础条件
wrapper.eq(User::getStatus, 1)
.like(User::getUsername, "张")
.between(User::getAge, 20, 30)
.orderByDesc(User::getCreateTime);
// 对应 SQL:
// SELECT * FROM user
// WHERE status = 1
// AND username LIKE '%张%'
// AND age BETWEEN 20 AND 30
// ORDER BY create_time DESC
List<User> users = userMapper.selectList(wrapper);
}
/**
* 条件构造 - 动态条件
*/
@Test
public void testDynamicCondition() {
UserQuery query = new UserQuery();
query.setUsername("张");
query.setMinAge(20);
query.setMaxAge(30);
query.setStatus(1);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 条件为 null 或空字符串时不加入 SQL
wrapper.like(StringUtils.hasText(query.getUsername()),
User::getUsername, query.getUsername())
.ge(query.getMinAge() != null, User::getAge, query.getMinAge())
.le(query.getMaxAge() != null, User::getAge, query.getMaxAge())
.eq(query.getStatus() != null, User::getStatus, query.getStatus());
List<User> users = userMapper.selectList(wrapper);
}
/**
* OR 条件
*/
@Test
public void testOrCondition() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 默认使用 AND 连接
wrapper.eq(User::getStatus, 1)
.eq(User::getAge, 25);
// SQL: WHERE status = 1 AND age = 25
// 使用 OR 连接
wrapper.clear();
wrapper.eq(User::getStatus, 1)
.or()
.eq(User::getAge, 25);
// SQL: WHERE status = 1 OR age = 25
// 嵌套 OR
wrapper.clear();
wrapper.eq(User::getStatus, 1)
.and(w -> w.eq(User::getUsername, "张三")
.or()
.eq(User::getEmail, "test@example.com"));
// SQL: WHERE status = 1 AND (username = '张三' OR email = 'test@example.com')
// OR 嵌套
wrapper.clear();
wrapper.eq(User::getStatus, 1)
.or(w -> w.eq(User::getUsername, "张三")
.eq(User::getEmail, "test@example.com"));
// SQL: WHERE status = 1 OR (username = '张三' AND email = 'test@example.com')
List<User> users = userMapper.selectList(wrapper);
}
/**
* SELECT 指定字段
*/
@Test
public void testSelectFields() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 指定查询字段
wrapper.select(User::getId, User::getUsername, User::getEmail);
// SQL: SELECT id, username, email FROM user
// 排除字段
wrapper.clear();
wrapper.select(User.class,
info -> !"extra".equals(info.getColumn()) &&
!"create_time".equals(info.getColumn()));
List<User> users = userMapper.selectList(wrapper);
}
/**
* 嵌套查询
*/
@Test
public void testNested() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// nested 方法
wrapper.nested(i -> i.eq(User::getStatus, 1)
.like(User::getUsername, "张"))
.between(User::getAge, 20, 30);
// SQL: WHERE (status = 1 AND username LIKE '%张%') AND age BETWEEN 20 AND 30
List<User> users = userMapper.selectList(wrapper);
}
/**
* EXISTS / NOT EXISTS
*/
@Test
public void testExists() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// EXISTS
wrapper.exists("SELECT 1 FROM blacklist WHERE user_id = {0}", 123);
// SQL: WHERE EXISTS (SELECT 1 FROM blacklist WHERE user_id = 123)
// NOT EXISTS
wrapper.notExists("SELECT 1 FROM blacklist WHERE user_id = {0}", 123);
List<User> users = userMapper.selectList(wrapper);
}
/**
* APPLY - 自定义 SQL 片段
*/
@Test
public void testApply() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 直接拼接 SQL(有注入风险)
wrapper.apply("date_format(create_time, '%Y-%m') = '2024-01'");
// SQL: WHERE date_format(create_time, '%Y-%m') = '2024-01'
// 使用参数绑定(推荐)
wrapper.apply("date_format(create_time, '%Y-%m') = {0}", "2024-01");
// SQL: WHERE date_format(create_time, '%Y-%m') = '2024-01'
List<User> users = userMapper.selectList(wrapper);
}
}
4.3 UpdateWrapper
/**
* UpdateWrapper - 更新条件构造器
*/
@SpringBootTest
public class UpdateWrapperTest {
@Autowired
private UserMapper userMapper;
/**
* 基础更新
*/
@Test
public void testBasicUpdate() {
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
// SET 子句
wrapper.set("status", 2)
.set("update_time", new Date())
.eq("id", 1L);
// SQL: UPDATE user SET status = 2, update_time = ? WHERE id = 1
int rows = userMapper.update(null, wrapper);
}
/**
* Lambda 更新
*/
@Test
public void testLambdaUpdate() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(User::getStatus, 2)
.set(User::getUpdateTime, new Date())
.eq(User::getId, 1L);
// SQL: UPDATE user SET status = 2, update_time = ? WHERE id = 1
int rows = userMapper.update(null, wrapper);
}
/**
* SET 条件更新
*/
@Test
public void testSetCondition() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
// 条件 SET(条件满足时才设置)
wrapper.set(true, User::getStatus, 2) // 条件为 true 时设置
.set(StringUtils.hasText("张"), User::getUsername, "李四")
.eq(User::getId, 1L);
int rows = userMapper.update(null, wrapper);
}
/**
* 增量更新
*/
@Test
public void testIncrement() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
// 使用 SQL 函数
wrapper.setSql("age = age + 1")
.eq(User::getId, 1L);
// SQL: UPDATE user SET age = age + 1 WHERE id = 1
int rows = userMapper.update(null, wrapper);
}
/**
* 批量更新
*/
@Test
public void testBatchUpdate() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(User::getStatus, 0)
.in(User::getId, Arrays.asList(1L, 2L, 3L));
// SQL: UPDATE user SET status = 0 WHERE id IN (1, 2, 3)
int rows = userMapper.update(null, wrapper);
}
/**
* Entity + Wrapper 更新
*/
@Test
public void testEntityUpdate() {
// 使用 entity 设置更新值
User user = new User();
user.setStatus(2);
user.setUpdateTime(new Date());
// 使用 wrapper 设置条件
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, 1L);
// entity 非空字段作为 SET 子句,wrapper 条件作为 WHERE 子句
int rows = userMapper.update(user, wrapper);
// SQL: UPDATE user SET status = 2, update_time = ? WHERE id = 1
}
}
五、IService 接口
5.1 IService 基础用法
/**
* IService - Service 层封装
*/
public interface UserService extends IService<User> {
// 自定义方法
List<User> selectByCustomCondition(UserQuery query);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
@Override
public List<User> selectByCustomCondition(UserQuery query) {
// 自定义实现
return baseMapper.selectList(
new LambdaQueryWrapper<User>()
.like(StringUtils.hasText(query.getUsername()),
User::getUsername, query.getUsername())
.eq(query.getStatus() != null, User::getStatus, query.getStatus())
);
}
}
/**
* IService 常用方法测试
*/
@SpringBootTest
public class IServiceTest {
@Autowired
private UserService userService;
/**
* 保存操作
*/
@Test
public void testSave() {
User user = new User();
user.setUsername("wangwu");
user.setEmail("wangwu@example.com");
user.setAge(28);
// 保存单个
boolean success = userService.save(user);
// 批量保存
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User u = new User();
u.setUsername("user" + i);
users.add(u);
}
boolean batchSuccess = userService.saveBatch(users);
// 批量保存(指定批次大小)
userService.saveBatch(users, 100);
// 保存或更新(根据 ID 判断)
userService.saveOrUpdate(user);
// 批量保存或更新
userService.saveOrUpdateBatch(users);
}
/**
* 查询操作
*/
@Test
public void testQuery() {
// 根据 ID 查询
User user = userService.getById(1L);
// 根据 Wrapper 查询一个
User one = userService.getOne(
new LambdaQueryWrapper<User>()
.eq(User::getUsername, "zhangsan")
);
// 查询列表
List<User> list = userService.list();
// 条件查询列表
List<User> activeList = userService.list(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.orderByDesc(User::getCreateTime)
);
// 查询所有 ID
List<Long> ids = userService.list().stream()
.map(User::getId)
.collect(Collectors.toList());
// 根据 ID 列表查询
List<User> users = userService.listByIds(ids);
// 查询总数
long count = userService.count();
// 条件查询总数
long activeCount = userService.count(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
);
// 查询 Map 列表
List<Map<String, Object>> maps = userService.listMaps();
// 查询 Objs(单列)
List<Object> usernames = userService.listObjs();
}
/**
* 更新操作
*/
@Test
public void testUpdate() {
User user = new User();
user.setId(1L);
user.setAge(30);
// 根据 ID 更新
boolean success = userService.updateById(user);
// 根据 Wrapper 更新
boolean updateSuccess = userService.update(
new LambdaUpdateWrapper<User>()
.set(User::getStatus, 2)
.eq(User::getId, 1L)
);
// 批量更新
List<User> users = new ArrayList<>();
// ... 添加用户
boolean batchSuccess = userService.updateBatchById(users);
// 批量更新(指定批次大小)
userService.updateBatchById(users, 100);
}
/**
* 删除操作
*/
@Test
public void testRemove() {
// 根据 ID 删除
boolean success = userService.removeById(1L);
// 根据 ID 列表删除
boolean batchSuccess = userService.removeByIds(
Arrays.asList(1L, 2L, 3L));
// 根据 Wrapper 删除
boolean removeSuccess = userService.remove(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 0)
);
}
/**
* 分页查询
*/
@Test
public void testPage() {
// 分页参数
int current = 1; // 当前页
int size = 10; // 每页大小
// 分页查询
Page<User> page = userService.page(
new Page<>(current, size),
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.orderByDesc(User::getCreateTime)
);
// 获取结果
List<User> records = page.getRecords(); // 数据列表
long total = page.getTotal(); // 总记录数
long pages = page.getPages(); // 总页数
long current1 = page.getCurrent(); // 当前页
long size1 = page.getSize(); // 每页大小
System.out.println("总记录数: " + total);
System.out.println("总页数: " + pages);
System.out.println("数据: " + records);
}
}
5.2 链式调用
/**
* IService 链式调用
*/
@SpringBootTest
public class ChainQueryTest {
@Autowired
private UserService userService;
/**
* 查询链式调用
*/
@Test
public void testQueryChain() {
// 链式查询
User user = userService.lambdaQuery()
.eq(User::getUsername, "zhangsan")
.one();
// 链式查询列表
List<User> list = userService.lambdaQuery()
.eq(User::getStatus, 1)
.like(User::getUsername, "张")
.orderByDesc(User::getCreateTime)
.list();
// 链式分页
Page<User> page = userService.lambdaQuery()
.eq(User::getStatus, 1)
.page(new Page<>(1, 10));
// 链式统计
long count = userService.lambdaQuery()
.eq(User::getStatus, 1)
.count();
// 链式条件判断
List<User> users = userService.lambdaQuery()
.eq(User::getStatus, 1)
.like(StringUtils.hasText("张"), User::getUsername, "张")
.between(User::getAge, 20, 30)
.list();
}
/**
* 更新链式调用
*/
@Test
public void testUpdateChain() {
// 链式更新
boolean success = userService.lambdaUpdate()
.set(User::getStatus, 2)
.set(User::getUpdateTime, new Date())
.eq(User::getId, 1L)
.update();
// 链式更新(带条件)
boolean updateSuccess = userService.lambdaUpdate()
.set(User::getStatus, 0)
.in(User::getId, Arrays.asList(1L, 2L, 3L))
.update();
// 链式删除
boolean removeSuccess = userService.lambdaUpdate()
.eq(User::getStatus, 0)
.remove();
}
}
六、分页插件
6.1 分页插件配置
/**
* 分页插件配置
*/
@Configuration
public class MyBatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInterceptor =
new PaginationInnerInterceptor(DbType.MYSQL);
// 设置最大单页限制数量(-1 不受限制)
paginationInterceptor.setMaxLimit(500L);
// 溢出总页数后是否进行处理
paginationInterceptor.setOverflow(true);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
/**
* 分页使用示例
*/
@SpringBootTest
public class PaginationTest {
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
/**
* 基础分页
*/
@Test
public void testBasicPage() {
Page<User> page = new Page<>(1, 10);
// 无条件分页
Page<User> result = userMapper.selectPage(page, null);
// 有条件分页
Page<User> result2 = userMapper.selectPage(
page,
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.orderByDesc(User::getCreateTime)
);
// 结果
System.out.println("当前页: " + result.getCurrent());
System.out.println("每页大小: " + result.getSize());
System.out.println("总记录数: " + result.getTotal());
System.out.println("总页数: " + result.getPages());
System.out.println("数据: " + result.getRecords());
}
/**
* 自定义分页
*/
public interface UserMapper extends BaseMapper<User> {
/**
* 自定义分页查询
*/
IPage<UserVO> selectUserPage(
Page<UserVO> page,
@Param("query") UserQuery query
);
}
/*
<!-- UserMapper.xml -->
<select id="selectUserPage" resultType="com.example.vo.UserVO">
SELECT u.*, d.name as dept_name
FROM user u
LEFT JOIN dept d ON u.dept_id = d.id
<where>
<if test="query.username != null and query.username != ''">
AND u.username LIKE CONCAT('%', #{query.username}, '%')
</if>
<if test="query.status != null">
AND u.status = #{query.status}
</if>
</where>
ORDER BY u.create_time DESC
</select>
*/
@Test
public void testCustomPage() {
Page<UserVO> page = new Page<>(1, 10);
UserQuery query = new UserQuery();
query.setUsername("张");
query.setStatus(1);
IPage<UserVO> result = userMapper.selectUserPage(page, query);
}
/**
* count 查询优化
*/
@Test
public void testCountQuery() {
Page<User> page = new Page<>(1, 10);
// 默认查询 count
Page<User> result1 = userMapper.selectPage(page, wrapper);
// 不查询 count(适用于不需要总数场景)
Page<User> page2 = new Page<>(1, 10, false);
Page<User> result2 = userMapper.selectPage(page2, wrapper);
// 自定义 count 查询
Page<User> page3 = new Page<>(1, 10);
page3.setOptimizeCountSql(true); // 优化 count SQL
}
}
七、代码生成器
7.1 MyBatis-Plus 代码生成器
/**
* MyBatis-Plus 代码生成器(3.5.x 新版)
*/
public class CodeGenerator {
public static void main(String[] args) {
// 数据库配置
DataSourceConfig.Builder dataSourceConfig =
new DataSourceConfig.Builder(
"jdbc:mysql://localhost:3306/mydb",
"root",
"123456"
);
// 创建代码生成器
AutoGenerator generator = new AutoGenerator(dataSourceConfig);
// 全局配置
GlobalConfig globalConfig = new GlobalConfig.Builder()
.outputDir(System.getProperty("user.dir") + "/src/main/java")
.author("author")
.enableSwagger() // 开启 Swagger 注解
.dateType(DateType.TIME_PACK) // 使用 java.time 包
.build();
// 包配置
PackageConfig packageConfig = new PackageConfig.Builder()
.parent("com.example")
.moduleName("system")
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.xml("mapper.xml")
.controller("controller")
.build();
// 策略配置
StrategyConfig strategyConfig = new StrategyConfig.Builder()
.addInclude("sys_user", "sys_dept") // 需要生成的表
.addTablePrefix("sys_", "tb_") // 表前缀过滤
// Entity 策略
.entityBuilder()
.enableLombok() // 使用 Lombok
.enableChainModel() // 链式模型
.enableTableFieldAnnotation() // 生成字段注解
.logicDeleteColumnName("is_deleted") // 逻辑删除字段
.versionColumnName("version") // 乐观锁字段
.addTableFills(
new Column("create_time", FieldFill.INSERT),
new Column("update_time", FieldFill.INSERT_UPDATE)
)
.formatFileName("%s")
// Controller 策略
.controllerBuilder()
.enableRestStyle() // REST 风格
.formatFileName("%sController")
// Service 策略
.serviceBuilder()
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
// Mapper 策略
.mapperBuilder()
.enableMapperAnnotation() // @Mapper 注解
.formatMapperFileName("%sMapper")
.formatXmlFileName("%sMapper")
.build();
// 注入配置
InjectionConfig injectionConfig = new InjectionConfig.Builder()
.beforeOutputFile((tableInfo, objectMap) -> {
System.out.println("生成文件: " + tableInfo.getEntityName());
})
.build();
// 配置生成器
generator.global(globalConfig)
.packageInfo(packageConfig)
.strategy(strategyConfig)
.injection(injectionConfig);
// 执行生成
generator.execute();
}
}
/**
* 生成的 Entity 示例
*/
@Data
@TableName("sys_user")
public class User implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("username")
private String username;
@TableField("email")
private String email;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic
@TableField("is_deleted")
private Integer deleted;
@Version
@TableField("version")
private Integer version;
}
/**
* 生成的 Mapper 示例
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
/**
* 生成的 Service 接口示例
*/
public interface UserService extends IService<User> {
}
/**
* 生成的 Service 实现示例
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
}
/**
* 生成的 Controller 示例
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.getById(id);
}
}
八、高级特性
8.1 逻辑删除
/**
* 逻辑删除配置与使用
*/
@SpringBootTest
public class LogicDeleteTest {
@Autowired
private UserMapper userMapper;
/**
* 实体类配置
*/
@Data
@TableName("sys_user")
public static class User {
@TableId
private Long id;
private String username;
@TableLogic
private Integer deleted; // 0-未删除,1-已删除
}
/**
* 全局配置(application.yml)
*/
/*
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
*/
/**
* 删除操作(实际执行 UPDATE)
*/
@Test
public void testLogicDelete() {
// 删除单条
int rows = userMapper.deleteById(1L);
// SQL: UPDATE sys_user SET deleted = 1 WHERE id = 1 AND deleted = 0
// 批量删除
int batchRows = userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));
// SQL: UPDATE sys_user SET deleted = 1 WHERE id IN (1, 2, 3) AND deleted = 0
// 条件删除
int condRows = userMapper.delete(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 0)
);
// SQL: UPDATE sys_user SET deleted = 1 WHERE status = 0 AND deleted = 0
}
/**
* 查询操作(自动过滤已删除数据)
*/
@Test
public void testSelectWithLogicDelete() {
// 自动添加 deleted = 0 条件
List<User> users = userMapper.selectList(null);
// SQL: SELECT * FROM sys_user WHERE deleted = 0
// 根据 ID 查询
User user = userMapper.selectById(1L);
// SQL: SELECT * FROM sys_user WHERE id = 1 AND deleted = 0
}
/**
* 查询包含已删除数据
*/
@Test
public void testSelectIncludeDeleted() {
// 方式1:在 Mapper 中自定义方法,忽略逻辑删除
// @InterceptorIgnore(tenantLine = "true")
// 方式2:使用原生 SQL
List<User> allUsers = userMapper.selectList(
new QueryWrapper<User>().apply("1=1 OR 1=1")
);
}
}
/**
* 自定义逻辑删除处理器
*/
@Component
public class CustomLogicDeleteHandler implements ISqlParser {
@Override
public void parser(MetaObject metaObject, String sql) {
// 自定义逻辑删除处理逻辑
}
}
8.2 乐观锁
/**
* 乐观锁配置与使用
*/
@Configuration
public class OptimisticLockerConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
/**
* 乐观锁使用示例
*/
@SpringBootTest
public class OptimisticLockerTest {
@Autowired
private ProductMapper productMapper;
/**
* 实体类
*/
@Data
@TableName("product")
public static class Product {
@TableId
private Long id;
private String name;
private Integer stock;
@Version
private Integer version;
}
/**
* 更新时自动处理版本号
*/
@Test
public void testOptimisticLock() {
// 1. 查询
Product product = productMapper.selectById(1L);
System.out.println("当前版本: " + product.getVersion()); // 例如 1
// 2. 修改
product.setStock(product.getStock() - 1);
// 3. 更新
int rows = productMapper.updateById(product);
// SQL: UPDATE product SET stock = ?, version = 2
// WHERE id = 1 AND version = 1
System.out.println("更新结果: " + rows); // 1-成功,0-失败
if (rows == 0) {
// 更新失败,数据已被其他线程修改
System.out.println("更新失败,请重试");
}
}
/**
* 并发场景测试
*/
@Test
public void testConcurrent() throws InterruptedException {
int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger failCount = new AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
Product product = productMapper.selectById(1L);
product.setStock(product.getStock() - 1);
int rows = productMapper.updateById(product);
if (rows > 0) {
successCount.incrementAndGet();
} else {
failCount.incrementAndGet();
}
} finally {
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("成功: " + successCount.get());
System.out.println("失败: " + failCount.get());
}
/**
* 重试机制
*/
@Test
public void testRetry() {
int maxRetry = 3;
int retry = 0;
boolean success = false;
while (retry < maxRetry && !success) {
Product product = productMapper.selectById(1L);
product.setStock(product.getStock() - 1);
int rows = productMapper.updateById(product);
if (rows > 0) {
success = true;
} else {
retry++;
System.out.println("重试第 " + retry + " 次");
}
}
if (!success) {
throw new RuntimeException("更新失败,请稍后重试");
}
}
}
8.3 多租户
/**
* 多租户配置
*/
@Configuration
public class TenantConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件
TenantLineInnerInterceptor tenantInterceptor =
new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 获取当前租户 ID
Long tenantId = TenantContext.getTenantId();
return new LongValue(tenantId);
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略的表(不需要租户过滤)
return "sys_config".equals(tableName) ||
"sys_tenant".equals(tableName);
}
@Override
public String getTenantIdColumn() {
// 租户字段名
return "tenant_id";
}
});
interceptor.addInnerInterceptor(tenantInterceptor);
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
/**
* 租户上下文
*/
public class TenantContext {
private static final ThreadLocal<Long> TENANT_ID = new ThreadLocal<>();
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
public static Long getTenantId() {
return TENANT_ID.get();
}
public static void clear() {
TENANT_ID.remove();
}
}
/**
* 租户拦截器示例
*/
@SpringBootTest
public class TenantTest {
@Autowired
private UserMapper userMapper;
@Test
public void testTenant() {
// 设置租户 ID
TenantContext.setTenantId(1L);
// 查询会自动添加租户条件
List<User> users = userMapper.selectList(null);
// SQL: SELECT * FROM user WHERE tenant_id = 1
// 插入会自动填充租户 ID
User user = new User();
user.setUsername("test");
userMapper.insert(user);
// SQL: INSERT INTO user(username, tenant_id) VALUES('test', 1)
}
/**
* 忽略租户过滤
*/
public interface UserMapper extends BaseMapper<User> {
@InterceptorIgnore(tenantLine = "true")
List<User> selectAllIgnoreTenant();
}
}
8.4 数据权限
/**
* 数据权限拦截器
*/
@Component
public class DataPermissionInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
// 获取当前用户的数据权限范围
DataScope dataScope = SecurityContext.getDataScope();
if (dataScope == null) {
return;
}
// 获取原始 SQL
String originalSql = boundSql.getSql();
// 解析并重写 SQL,添加数据权限条件
String newSql = addDataPermissionCondition(originalSql, dataScope);
// 更新 SQL
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", newSql);
}
private String addDataPermissionCondition(String sql, DataScope dataScope) {
// 使用 JSqlParser 解析 SQL
try {
Statement statement = CCJSqlParserUtil.parse(sql);
if (statement instanceof Select) {
Select select = (Select) statement;
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
Expression where = plainSelect.getWhere();
Expression dataPermissionExpr = buildDataPermissionExpression(dataScope);
if (where == null) {
plainSelect.setWhere(dataPermissionExpr);
} else {
plainSelect.setWhere(new AndExpression(where, dataPermissionExpr));
}
}
return statement.toString();
} catch (JSQLParserException e) {
throw new RuntimeException("SQL 解析失败", e);
}
}
private Expression buildDataPermissionExpression(DataScope dataScope) {
// 根据数据权限构建条件表达式
// 例如:dept_id IN (1, 2, 3)
return new InExpression(
new Column("dept_id"),
new ExpressionList(
dataScope.getDeptIds().stream()
.map(id -> new LongValue(id))
.collect(Collectors.toList())
)
);
}
}
/**
* 数据权限配置
*/
@Configuration
public class DataPermissionConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限插件
interceptor.addInnerInterceptor(new DataPermissionInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
九、总结与最佳实践
9.1 核心优势总结
┌─────────────────────────────────────────────────────────────────────────┐
│ MyBatis-Plus 核心优势 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 无侵入设计 │
│ - 只做增强不做改变 │
│ - 兼容原生 MyBatis 所有功能 │
│ │
│ 2. 强大的 CRUD 操作 │
│ - BaseMapper 提供通用 CRUD │
│ - IService 提供更丰富的业务方法 │
│ │
│ 3. 条件构造器 │
│ - QueryWrapper / LambdaQueryWrapper │
│ - UpdateWrapper / LambdaUpdateWrapper │
│ - 链式调用,代码优雅 │
│ │
│ 4. 丰富的插件 │
│ - 分页插件(物理分页) │
│ - 乐观锁插件 │
│ - 多租户插件 │
│ - 数据权限插件 │
│ │
│ 5. 开发效率 │
│ - 代码生成器 │
│ - 自动填充 │
│ - 逻辑删除 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
9.2 最佳实践
-
实体设计
- 使用
@TableName明确表名 - 使用
@TableId指定主键策略 - 逻辑删除、乐观锁字段统一命名
- 使用
-
条件构造
- 优先使用 LambdaWrapper 避免字段名硬编码
- 使用条件构造器实现动态 SQL
- 复杂查询考虑自定义 SQL
-
性能优化
- 合理使用索引
- 避免 N+1 查询
- 批量操作使用 saveBatch/updateBatchById
-
插件使用
- 分页插件配置最大限制
- 多租户注意忽略特殊表
- 数据权限考虑性能影响
参考资料:
- MyBatis-Plus 官方文档:baomidou.com/
- MyBatis-Plus GitHub:github.com/baomidou/my…
六、思考与练习
思考题
-
基础题:MyBatis-Plus 的 BaseMapper 提供了哪些通用 CRUD 方法?与传统 MyBatis 相比有什么优势?
-
进阶题:LambdaQueryWrapper 相比 QueryWrapper 有什么优势?在什么场景下应该优先使用 Lambda 表达式?
-
实战题:在一个电商系统中,如何使用 MyBatis-Plus 实现商品库存的并发扣减?需要考虑哪些并发问题?
编程练习
练习:基于 MyBatis-Plus 实现一个用户管理系统,要求:
- 使用 Lambda 条件构造器实现多条件动态查询(用户名模糊、状态精确、年龄范围)
- 实现分页查询功能,每页显示 10 条记录
- 使用 @TableLogic 实现逻辑删除
- 使用 @Version 实现乐观锁,防止并发修改冲突
- 使用代码生成器生成 Entity、Mapper、Service、Controller 代码
章节关联
- 前置章节:MyBatis 核心原理
- 后续章节:Spring Data JPA 详解
- 扩展阅读:
- MyBatis 插件开发指南
- 数据库性能优化实践
- 分布式事务解决方案
📝 下一章预告
下一章将深入讲解 Spring Data JPA,这是 Spring 生态中另一个重要的数据访问框架。我们将对比 JPA 与 MyBatis-Plus 的设计理念、使用场景和性能特点,帮助你在实际项目中选择最合适的数据访问方案。
本章完