MyBatis Plus 实战指南:从入门到精通
MyBatis 作为一款优秀的持久层框架,在国内 Java 开发领域有着广泛的应用。它通过 XML 或注解的方式将 SQL 语句与 Java 对象进行映射,提供了灵活的 SQL 控制能力。然而,在实际开发中,尤其是面对大量的单表 CRUD 操作时,手动编写 Mapper XML 或注解依然显得有些繁琐。为了解决这个问题,MyBatis Plus (简称 MP) 应运而生。
MyBatis Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,旨在简化开发、提高效率。它提供了许多实用的功能,如通用的 CRUD 操作、强大的条件构造器、分页插件、逻辑删除、乐观锁、代码生成器等,极大地减少了开发者的编码量,让开发者能更专注于业务逻辑。
本文将作为一份详尽的实战指南,带你一步步深入了解并掌握 MyBatis Plus 的核心功能和最佳实践。
一、 为什么选择 MyBatis Plus?
在深入实战之前,我们先明确选择 MP 的理由:
- 无侵入性:MP 在 MyBatis 基础上进行扩展,完全兼容原生 MyBatis 的所有功能。你可以混合使用 MP 的功能和 MyBatis 的原生功能。
- 极简 CRUD:对于单表的增删改查操作,MP 提供了通用的
BaseMapper
接口,内置了大量常用方法,无需编写任何 SQL 语句即可完成。 - 强大的条件构造器:通过
Wrapper
(如QueryWrapper
,LambdaQueryWrapper
),可以非常方便地构建复杂的查询条件,支持链式调用,代码可读性强且类型安全(LambdaQueryWrapper)。 - 内置分页插件:只需简单配置,即可实现物理分页,无需关心具体数据库的分页语法差异。
- 内置逻辑删除:通过简单的注解和配置,实现软删除,查询时自动过滤已删除数据。
- 内置乐观锁:通过
@Version
注解和配置,轻松实现乐观锁机制,解决并发更新问题。 - 自动填充:方便地对实体类中的
createTime
、updateTime
等字段进行自动填充。 - 代码生成器:强大的代码生成引擎,可以一键生成 Entity、Mapper、Service、Controller 等代码,极大提升开发效率。
- 性能分析插件:可以输出 SQL 分析日志,帮助开发者优化 SQL 语句。
- 活跃的社区和文档:MP 拥有活跃的中文社区和完善的官方文档,遇到问题容易找到解决方案。
二、 快速开始:集成与配置
我们以 Spring Boot 项目为例,演示如何快速集成 MyBatis Plus。
1. 添加依赖 (Maven)
“`xml
“`
2. 配置 application.yml (或 application.properties)
“`yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/*/.xml # 如果需要使用 XML,指定位置
global-config:
db-config:
# 主键策略 (默认为ASSIGN_ID, 雪花算法)
id-type: assign_id
# 逻辑删除配置 (如果全局配置)
# logic-delete-field: deleted # 全局逻辑删除的实体字段名(全局注入)
# logic-delete-value: 1 # 逻辑已删除值(默认为 1)
# logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
configuration:
# 开启驼峰命名转换: true (默认为 true)
map-underscore-to-camel-case: true
# 配置日志实现,方便调试查看 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
“`
3. 扫描 Mapper 接口
在 Spring Boot 的启动类上添加 @MapperScan
注解,指定 Mapper 接口所在的包路径。
“`java
package com.example.myapp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(“com.example.myapp.mapper”) // 指定你的 Mapper 接口包路径
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
“`
4. 创建实体类 (Entity)
使用注解标识表名、主键、字段等信息。
“`java
package com.example.myapp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data // Lombok 注解,自动生成 Getter/Setter/toString 等
@TableName(“sys_user”) // 指定数据库表名
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID) // 指定主键及生成策略 (雪花算法)
private Long id;
@TableField("user_name") // 指定数据库字段名 (如果属性名与字段名符合驼峰转下划线规则,可省略)
private String name;
private Integer age;
private String email;
// 逻辑删除字段 (配合后面讲解的逻辑删除功能)
@TableLogic
private Integer deleted;
// 乐观锁字段 (配合后面讲解的乐观锁功能)
@Version
private Integer version;
// 创建时间 (配合后面讲解的自动填充功能)
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 更新时间 (配合后面讲解的自动填充功能)
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
“`
5. 创建 Mapper 接口
只需继承 BaseMapper<T>
即可拥有强大的 CRUD 能力。
“`java
package com.example.myapp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.myapp.entity.User;
// 无需编写任何方法,已继承 BaseMapper 的通用 CRUD
public interface UserMapper extends BaseMapper
// 如果需要自定义 SQL,可以像原生 MyBatis 一样在这里定义方法,并在 XML 或使用注解实现
// List
}
“`
至此,基础的集成和配置已经完成,你可以开始使用 MP 提供的便捷功能了。
三、 核心功能实战
1. 通用 CRUD 操作
BaseMapper
提供了丰富的开箱即用的方法:
“`java
package com.example.myapp.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.myapp.entity.User;
import com.example.myapp.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 新增用户
public void addUser(User user) {
// INSERT INTO sys_user ( id, user_name, age, email, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ? )
int result = userMapper.insert(user);
System.out.println("新增结果:" + result + ", 用户ID: " + user.getId());
}
// 根据 ID 删除用户
public void deleteUser(Long id) {
// DELETE FROM sys_user WHERE id=? (物理删除)
// UPDATE sys_user SET deleted=1 WHERE id=? AND deleted=0 (逻辑删除,如果配置了)
int result = userMapper.deleteById(id);
System.out.println("删除结果:" + result);
}
// 根据 ID 批量删除
public void deleteBatchIds(List<Long> ids) {
// DELETE FROM sys_user WHERE id IN ( ? , ? , ? ) (物理删除)
// UPDATE sys_user SET deleted=1 WHERE id IN ( ? , ? , ? ) AND deleted=0 (逻辑删除)
int result = userMapper.deleteBatchIds(ids);
System.out.println("批量删除结果:" + result);
}
// 根据 ID 修改用户
public void updateUser(User user) {
// UPDATE sys_user SET user_name=?, age=?, email=?, update_time=? WHERE id=? (如果配置了乐观锁,会带上 version 条件)
// 注意:只会更新 user 对象中非 null 的字段
int result = userMapper.updateById(user);
System.out.println("修改结果:" + result);
}
// 根据 ID 查询用户
public User getUserById(Long id) {
// SELECT id,user_name,age,email,deleted,version,create_time,update_time FROM sys_user WHERE id=? (如果配置了逻辑删除,会带上 deleted=0)
User user = userMapper.selectById(id);
System.out.println("查询结果:" + user);
return user;
}
// 查询所有用户
public List<User> listAllUsers() {
// SELECT id,user_name,age,email,deleted,version,create_time,update_time FROM sys_user (如果配置了逻辑删除,会带上 deleted=0)
List<User> users = userMapper.selectList(null); // 传入 null 表示无条件查询
System.out.println("查询所有用户:" + users.size());
return users;
}
// 根据条件查询用户列表
public List<User> listUsersByCondition() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("user_name", "张") // WHERE user_name LIKE '%张%'
.ge("age", 20) // AND age >= 20
.orderByDesc("create_time"); // ORDER BY create_time DESC
// SELECT id,user_name,age,email,deleted,version,create_time,update_time FROM sys_user WHERE deleted=0 AND user_name LIKE ? AND age >= ? ORDER BY create_time DESC
List<User> users = userMapper.selectList(queryWrapper);
System.out.println("条件查询用户:" + users.size());
return users;
}
// 分页查询用户
public IPage<User> listUsersByPage(int current, int size) {
// 1. 配置分页插件 (见下文)
// 2. 执行分页查询
Page<User> page = new Page<>(current, size);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 18); // 查询年龄大于等于 18 的用户
// SELECT COUNT(*) FROM sys_user WHERE deleted=0 AND age >= ?
// SELECT id,user_name,age,email,deleted,version,create_time,update_time FROM sys_user WHERE deleted=0 AND age >= ? LIMIT ?,?
IPage<User> userPage = userMapper.selectPage(page, queryWrapper);
System.out.println("总记录数:" + userPage.getTotal());
System.out.println("总页数:" + userPage.getPages());
System.out.println("当前页数据:" + userPage.getRecords().size());
return userPage;
}
}
“`
2. 条件构造器 (Wrapper
)
Wrapper
是 MP 的精髓之一,用于构建 SQL 的 WHERE 条件、ORDER BY、SELECT 字段等。主要有两种实现:
QueryWrapper
: 使用字符串指定列名,灵活但有硬编码风险。LambdaQueryWrapper
: 使用 Lambda 表达式(方法引用),类型安全,IDE 支持更好,重构友好,推荐使用。
“`java
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
// LambdaQueryWrapper 示例 (推荐)
LambdaQueryWrapper
lambdaQuery.like(User::getName, “王”) // like ‘%王%’
.between(User::getAge, 20, 30) // age between 20 and 30
.isNotNull(User::getEmail) // email is not null
.orderByAsc(User::getAge) // order by age asc
.select(User.class, info -> !info.getColumn().equals(“password”)); // 查询除 password 外的所有字段
List
// QueryWrapper 示例
QueryWrapper
queryWrapper.like(“user_name”, “李”)
.eq(“email”, “[email protected]”)
.in(“age”, Arrays.asList(25, 26, 27))
.last(“limit 1”); // 拼接 SQL 到末尾 (慎用)
User oneUser = userMapper.selectOne(queryWrapper); // 查询一条记录,多于一条会报错
// UpdateWrapper / LambdaUpdateWrapper 用于构建更新条件
LambdaUpdateWrapper
lambdaUpdate.eq(User::getName, “张三”) // 更新条件 WHERE name = ‘张三’
.set(User::getEmail, “[email protected]”) // SET email = ?
.set(User::getAge, null); // SET age = NULL
int updateCount = userMapper.update(null, lambdaUpdate); // 第一个参数为 null 表示不使用实体对象更新,而是根据 set() 方法更新
UpdateWrapper
updateWrapper.eq(“user_name”, “李四”)
.setSql(“age = age + 1”); // 使用 SQL 片段
userMapper.update(null, updateWrapper);
“`
常用条件方法:
eq()
: 等于=
ne()
: 不等于<>
gt()
: 大于>
ge()
: 大于等于>=
lt()
: 小于<
le()
: 小于等于<=
like()
:LIKE '%值%'
notLike()
:NOT LIKE '%值%'
likeLeft()
:LIKE '%值'
likeRight()
:LIKE '值%'
between()
:BETWEEN 值1 AND 值2
notBetween()
:NOT BETWEEN 值1 AND 值2
in()
:IN (值1, 值2...)
notIn()
:NOT IN (值1, 值2...)
isNull()
:IS NULL
isNotNull()
:IS NOT NULL
orderByAsc()
:ORDER BY 字段 ASC
orderByDesc()
:ORDER BY 字段 DESC
groupBy()
:GROUP BY 字段
having()
:HAVING ...
or()
: 添加OR
连接符and()
: 添加AND
连接符 (默认)nested()
: 嵌套括号( condition1 or condition2 )
apply()
: 拼接原生 SQL 片段select()
: 指定查询的字段
3. 分页查询 (PaginationInterceptor
)
MP 的分页功能通过拦截器实现,只需配置即可。
配置分页插件 Bean:
“`java
package com.example.myapp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件,并指定数据库类型 (这里是 MySQL)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
“`
使用分页查询: (参考前面 UserService
中的 listUsersByPage
方法)
创建 Page
对象(传入当前页码 current
和每页大小 size
),将其作为第一个参数传递给 selectPage
方法即可。返回的 IPage
对象包含了分页所需的所有信息(总记录数、总页数、当前页数据列表等)。
4. 逻辑删除 (@TableLogic
)
逻辑删除是指在删除数据时,并不是真正从数据库中物理删除,而是通过一个状态字段来标记该记录为“已删除”。MP 对逻辑删除提供了原生支持。
配置方式:
- 全局配置 (application.yml):
yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 已删除状态值
logic-not-delete-value: 0 # 未删除状态值 - 实体类注解配置 (推荐): 在实体类的逻辑删除字段上添加
@TableLogic
注解。
java
@TableLogic
@TableField("is_deleted") // 如果字段名不是 deleted,需要指定
private Integer deleted; // 字段类型可以是 Integer, Boolean 等
如果使用注解,可以不进行全局配置。注解指定的value
(已删除) 和delval
(未删除) 优先级更高。
效果:
- 执行
userMapper.deleteById(id)
或userMapper.delete(wrapper)
时,实际执行的是UPDATE sys_user SET deleted = 1 WHERE id = ? AND deleted = 0
。 - 执行所有
SELECT
查询(如selectById
,selectList
,selectPage
等)时,MP 会自动在 WHERE 条件中追加AND deleted = 0
(或配置的未删除值)。 - 如果确实需要查询包括已逻辑删除的数据,需要自己编写 SQL 或使用特定的 Wrapper 方法(不推荐,破坏了逻辑删除的封装性)。
5. 乐观锁 (@Version
)
乐观锁是一种并发控制机制,它假设在大多数情况下,数据更新不会发生冲突。在更新时,通过比较版本号来判断数据是否被其他事务修改过。
配置方式:
- 在实体类中添加版本号字段,并使用
@Version
注解标记:
java
@Version
private Integer version; -
配置乐观锁插件 Bean:
“`java
package com.example.myapp.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; // 引入乐观锁插件
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MybatisPlusConfig {@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 添加分页插件 (注意插件添加顺序) interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }
}
“`
效果:
- 当执行
userMapper.updateById(user)
或userMapper.update(user, wrapper)
时:- MP 会先根据 ID (和 Wrapper 条件) 查询出当前记录的版本号。
- 在执行 UPDATE 语句时,会将查询到的版本号作为 WHERE 条件的一部分 (
WHERE id = ? AND version = ?
),同时在 SET 子句中将版本号加 1 (SET ..., version = version + 1
)。 - 如果更新时,数据库中的版本号与查询到的版本号不一致(说明数据已被其他线程修改),则 UPDATE 语句的 WHERE 条件不满足,更新失败 (返回影响行数为 0)。
使用乐观锁注意事项:
- 版本号字段必须存在于数据库表中。
- 只有
updateById
和update(entity, wrapper)
方法支持乐观锁。update(null, wrapper)
这种不带实体参数的更新不支持。 - 需要在业务代码中判断更新操作的返回值(影响行数),如果为 0,则表示更新失败(可能发生了并发冲突),需要进行相应的处理(如重试、提示用户等)。
6. 自动填充 (MetaObjectHandler
)
在插入或更新记录时,经常需要自动设置创建时间、更新时间、创建人、修改人等字段。MP 提供了 MetaObjectHandler
来实现此功能。
配置方式:
-
创建
MetaObjectHandler
实现类:
“`java
package com.example.myapp.handler;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;@Slf4j
@Component // 将处理器注入到 Spring 容器中
public class MyMetaObjectHandler implements MetaObjectHandler {@Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); // setFieldValByName(字段名, 字段值, metaObject) // 注意:这里是实体类的属性名,不是数据库字段名 this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class); // 起始版本 3.3.0 可用(推荐) // 或者 // this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 旧版方式 // this.setFieldValByName("createTime", LocalDateTime.now(), metaObject); // 旧版可能覆盖用户设置的值 this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class); // 也可以填充创建人、修改人等字段,需要获取当前用户信息 // Object currentUserId = getCurrentUserId(); // 假设有方法获取当前用户ID // this.strictInsertFill(metaObject, "creatorId", () -> currentUserId, Long.class); // this.strictInsertFill(metaObject, "updaterId", () -> currentUserId, Long.class); } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class); // 起始版本 3.3.0 可用(推荐) // 或者 // this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 旧版方式 // this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); // 旧版可能覆盖用户设置的值 // Object currentUserId = getCurrentUserId(); // 假设有方法获取当前用户ID // this.strictUpdateFill(metaObject, "updaterId", () -> currentUserId, Long.class); }
}
2. **在实体类需要自动填充的字段上添加 `@TableField` 注解,并指定 `fill` 属性:**
java
@TableField(fill = FieldFill.INSERT) // 插入时填充
private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时都填充
private LocalDateTime updateTime;// @TableField(value = “creator”, fill = FieldFill.INSERT) // 指定数据库字段名和填充策略
// private Long creatorId;// @TableField(value = “updater”, fill = FieldFill.INSERT_UPDATE)
// private Long updaterId;
``
FieldFill枚举可选值:
DEFAULT
*: 默认不处理。
INSERT
*: 插入时填充。
UPDATE
*: 更新时填充。
INSERT_UPDATE`: 插入和更新时都填充。
*
效果:
- 当执行
insert()
操作时,insertFill()
方法会被调用,自动填充标记为FieldFill.INSERT
或FieldFill.INSERT_UPDATE
的字段。 - 当执行
updateById()
或update()
操作时,updateFill()
方法会被调用,自动填充标记为FieldFill.UPDATE
或FieldFill.INSERT_UPDATE
的字段。
strictXxxFill
系列方法相比旧的 setFieldValByName
的优势在于,它会先判断字段值是否为 null,只有在为 null 的情况下才进行填充,避免覆盖用户手动设置的值。
7. 代码生成器 (AutoGenerator
)
MyBatis Plus 的代码生成器(mybatis-plus-generator
)是一个非常强大的工具,可以根据数据库表结构自动生成 Entity、Mapper(接口和 XML)、Service(接口和实现类)、Controller 等代码,大幅减少重复劳动,提高开发效率。
使用方式:
- 添加生成器依赖:
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version> <!-- 版本建议与 MP 核心库保持一致或兼容 -->
</dependency>
<!-- 模板引擎 (如 Velocity 或 Freemarker) -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- Freemarker 引擎 -->
<!--
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
--> -
编写代码生成器配置类: 创建一个单独的 Java 类来配置和执行代码生成。
“`java
package com.example.myapp.generator;import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; // 或 FreemarkerTemplateEngine
import java.util.Collections;public class CodeGenerator {
public static void main(String[] args) { // 数据库连接配置 String dbUrl = "jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; String username = "your_username"; String password = "your_password"; // 项目路径 (根据你的实际项目结构调整) String projectPath = System.getProperty("user.dir"); // 获取当前项目路径 FastAutoGenerator.create(dbUrl, username, password) .globalConfig(builder -> { builder.author("Your Name") // 设置作者 .enableSwagger() // 开启 swagger 模式 (可选) .outputDir(projectPath + "/src/main/java") // 指定输出目录 .commentDate("yyyy-MM-dd"); // 注释日期格式 }) .packageConfig(builder -> { builder.parent("com.example.myapp") // 设置父包名 .moduleName("generated") // 设置父包模块名 (可选) .entity("entity") // Entity 包名 .service("service") // Service 包名 .serviceImpl("service.impl") // Service Impl 包名 .mapper("mapper") // Mapper 包名 .xml("mapper.xml") // Mapper XML 文件路径 (相对于 resources 目录) .controller("controller") // Controller 包名 .pathInfo(Collections.singletonMap(OutputFile.xml, projectPath + "/src/main/resources/mapper/generated")); // 设置 XML 输出路径 }) .strategyConfig(builder -> { builder.addInclude("sys_user", "sys_role") // 设置需要生成的表名 (可变参数) //.addTablePrefix("t_", "c_") // 设置过滤表前缀 .entityBuilder() // 实体策略配置 .enableLombok() // 启用 Lombok .enableTableFieldAnnotation() // 生成 @TableField 注解 .logicDeleteColumnName("deleted") // 指定逻辑删除字段名 .versionColumnName("version") // 指定乐观锁字段名 .controllerBuilder() // Controller 策略配置 .enableRestStyle() // 开启 @RestController 注解 .enableHyphenStyle() // 开启驼峰转连字符 (如 /user-info) .serviceBuilder() // Service 策略配置 .formatServiceFileName("%sService") // Service 接口文件命名 %s 为占位符 .formatServiceImplFileName("%sServiceImpl") // Service 实现类文件命名 .mapperBuilder() // Mapper 策略配置 .enableBaseResultMap() // 启用 BaseResultMap 生成 .enableBaseColumnList() // 启用 BaseColumnList 生成 .formatMapperFileName("%sMapper") .formatXmlFileName("%sMapper"); }) .templateEngine(new VelocityTemplateEngine()) // 使用 Velocity 模板引擎 (需要添加依赖) // .templateEngine(new FreemarkerTemplateEngine()) // 或者使用 Freemarker .execute(); // 执行生成 }
}
“` -
运行配置类: 直接运行
CodeGenerator
的main
方法,代码就会生成到指定的目录。
代码生成器提供了非常丰富的配置选项,可以根据项目需求进行高度定制,建议详细阅读官方文档以了解更多配置项。
四、 进阶与最佳实践
- 优先使用 LambdaWrapper:
LambdaQueryWrapper
和LambdaUpdateWrapper
提供了编译时类型检查,更安全,且便于代码重构。 - 理解 Wrapper 的链式调用: 合理利用链式调用可以使代码更简洁,但过长的链式调用可能影响可读性,需要权衡。
- Service 层封装: 尽量将 Mapper 的调用封装在 Service 层,保持 Controller 层的简洁。可以在 Service 层对 MP 的通用方法进行二次封装,加入业务逻辑。
- 复杂 SQL 处理: 对于非常复杂的查询(如多表关联、复杂统计等),MP 的 Wrapper 可能难以表达或性能不佳。这时可以回归 MyBatis 的本质,使用 XML 文件或 Mapper 接口注解来编写原生 SQL。MP 与原生 MyBatis 可以无缝共存。
- 性能考虑:
- 避免在循环中频繁调用数据库操作。考虑使用批量操作 (
saveBatch
,updateBatchById
等,虽然 Service 层默认没提供,但可以自己扩展或使用三方库如mybatis-plus-extension
中的ServiceImpl
的批量方法)。 - 使用
select
方法指定查询字段,避免select *
,尤其是在大表或字段较多的情况下。 - 合理使用数据库索引。
- 开启 MP 的性能分析插件 (开发和测试环境),监控慢 SQL。
- 避免在循环中频繁调用数据库操作。考虑使用批量操作 (
- 事务管理: MP 本身不负责事务管理,事务应交由 Spring 等容器管理(使用
@Transactional
注解)。 - 插件顺序: 在配置
MybatisPlusInterceptor
时,内部插件(InnerInterceptor)的添加顺序可能影响执行结果,例如分页插件通常建议放在后面。 - 版本升级: 关注 MP 的版本更新,新版本通常会带来性能优化、新功能和 Bug 修复。升级时注意查看官方的 Release Notes,了解是否有不兼容的变更。
五、 总结
MyBatis Plus 作为 MyBatis 的强力辅助,通过提供通用的 CRUD、强大的条件构造器、便捷的分页、逻辑删除、乐观锁、自动填充以及高效的代码生成器等功能,极大地简化了持久层的开发工作量,让开发者能够更加聚焦于业务本身。其无侵入性的设计也保证了与原生 MyBatis 的良好兼容性。
掌握 MyBatis Plus 不仅仅是学会使用它的 API,更重要的是理解其设计理念和各项特性背后的原理,并结合项目实际情况灵活运用。希望这份实战指南能帮助你更好地在项目中应用 MyBatis Plus,提升开发效率和代码质量。不断实践和探索,你会发现 MP 还有更多值得挖掘的潜力。