01. 核心组件:IService与BaseMapper的精准使用
IService 是MyBatis-Plus封装的服务层核心接口,内置了 save、update、list、page 等海量开箱即用的方法,能覆盖90%以上的单表CRUD场景,直接调用就能省掉大量重复代码。
BaseMapper 则是自定义SQL的主战场,当遇到复杂联表查询、多表关联统计等场景时,基于它编写自定义XML或注解SQL,能灵活应对复杂业务需求。
// 示例1:IService Lambda查询(简洁高效)
List<User> users = userService.lambdaQuery()
.eq(User::getStatus, 1) // 状态为启用
.like(User::getName, "张") // 姓名包含“张”
.list(); // 执行查询并返回列表
// 示例2:BaseMapper自定义查询(复杂场景)
// 需在UserMapper中定义方法,配合XML实现联表查询
UserVO selectUserDetail(Long userId);
小贴士:别把复杂SQL硬塞在Service层,该用BaseMapper写自定义SQL时千万别偷懒,清晰的分层才是代码易维护的关键。
02. Lambda查询:编译期规避字段名错误
核心优势
MyBatis-Plus的 Lambda查询 是最实用的特性之一,相比传统字符串拼接字段名的方式,它能在编译期就校验字段名是否正确,彻底避免运行时才发现“字段名拼错”的低级问题。
// 错误示例:字符串拼接字段名,拼错“name”为“naem”,编译不报错,运行才出问题
userService.lambdaQuery().eq("naem", "张三");
// 正确示例:Lambda表达式引用实体字段,编译期就能检测字段名错误
userService.lambdaQuery().eq(User::getName, "张三");
03. 分页查询:条件构造器+分页无缝集成
核心能力
MyBatis-Plus的分页功能深度适配自身条件构造器,支持自定义排序、分页结果转换,是后端分页场景的“最优解”,无需手动拼接分页SQL。
// 1. 构建分页对象:参数1=页码,参数2=每页条数
Page<User> page = new Page<>(1, 20);
// 2. 添加排序规则:按创建时间降序
page.addOrder(OrderItem.desc("create_time"));
// 3. 执行分页查询:结合Lambda条件构造器
Page<User> userPage = userService.page(page,
Wrappers.lambdaQuery(User.class)
.eq(User::getDeptId, 2) // 筛选部门ID为2的用户
);
// 4. 分页结果转换:User实体转UserVO(避免返回冗余字段)
Page<UserVO> voPage = userPage.convert(user -> {
UserVO vo = new UserVO();
// 拷贝属性(可使用Spring的BeanUtils或MapStruct)
BeanUtils.copyProperties(user, vo);
return vo;
});
04. 批量操作:解决大批量数据插入/更新性能问题
核心场景
如果大批量数据插入/更新时还逐条处理,数据库IO会被打满,性能直线下降。MyBatis-Plus的批量操作方法能按批次提交,大幅提升效率。
// 示例1:批量插入(指定批次大小,推荐1000条/批)
List<User> hugeUserList = new ArrayList<>(); // 待插入的海量用户数据
// 第二个参数为批次大小,控制每次提交的条数,避免数据库压力过大
userService.saveBatch(hugeUserList, 1000);
// 示例2:批量更新状态(按ID批量更新)
public void batchUpdateStatus(List<Long> ids, Integer status) {
// 1. 转换ID列表为待更新的User对象列表
List<User> updateList = ids.stream().map(id -> {
User user = new User();
user.setId(id); // 设置要更新的用户ID
user.setStatus(status); // 设置新状态
return user;
}).collect(Collectors.toList());
// 2. 批量更新(按ID更新)
userService.updateBatchById(updateList);
}
05. 复杂查询:QueryWrapper构建多条件逻辑
核心能力
QueryWrapper(及Lambda版 LambdaQueryWrapper)能灵活构建多条件、多逻辑的查询语句,支持 and/or 嵌套、区间查询、指定字段查询等场景。
// 示例1:多条件嵌套查询(and/or组合)
List<User> users = userService.lambdaQuery()
.eq(User::getStatus, 1) // 基础条件:状态启用
.and(wrapper -> wrapper // 嵌套条件:姓名含“张” 或 邮箱含“zhang”
.like(User::getName, "张")
.or()
.like(User::getEmail, "zhang")
)
.between(User::getCreateTime, startTime, endTime) // 时间区间:创建时间在start-end之间
.list();
// 示例2:指定字段查询(避免返回冗余字段,提升查询效率)
List<User> userList = userService.lambdaQuery()
.select(User::getId, User::getName) // 只查询ID和姓名字段
.eq(User::getStatus, 1) // 状态启用
.list();
06. 自动填充:告别手动设置公共字段
核心场景
create_time(创建时间)、update_time(更新时间)、create_by(创建人)这类公共字段,无需每次手动 set,通过MyBatis-Plus的自动填充功能就能自动赋值。
// 1. 实现MetaObjectHandler接口,自定义填充逻辑
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入操作时填充(如createTime、createBy)
@Override
public void insertFill(MetaObject metaObject) {
// strictInsertFill:严格模式填充,确保字段存在且未赋值时才填充
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUser()); // 获取当前登录用户
}
// 更新操作时填充(如updateTime)
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
// 模拟获取当前登录用户(实际项目中可从ThreadLocal/Token中获取)
private String getCurrentUser() {
return "admin";
}
}
// 2. 实体类定义对应字段
public class User {
// 自动填充的创建时间
private LocalDateTime createTime;
// 自动填充的更新时间
private LocalDateTime updateTime;
}
07. 逻辑删除:避免物理删除导致的数据丢失
核心价值
生产环境中绝对不要用 delete from 物理删除数据!MyBatis-Plus的逻辑删除能将“删除”操作转为“更新标记”,既保留数据,又不影响业务查询。
# 1. 配置逻辑删除(application.yml)
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段名(实体类需对应)
logic-delete-value: 1 # 已删除标记值
logic-not-delete-value: 0 # 未删除标记值
// 2. 实体类添加逻辑删除字段
public class User {
private Long id;
private String name;
// 逻辑删除标记(0=未删,1=已删)
private Integer deleted;
}
// 3. 调用删除方法(实际执行UPDATE而非DELETE)
userService.removeById(1L);
// 实际执行SQL:UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
// 所有查询会自动拼接:AND deleted = 0,无需手动加条件
08. 枚举映射:让状态字段更具可读性
核心场景
数据库中存储的状态码(如 status=1),在代码中直接用数字会导致可读性差,MyBatis-Plus支持枚举与数据库字段的自动映射,代码更清晰。
// 1. 定义状态枚举(与数据库状态码对应)
public enum UserStatus {
ENABLED(1, "启用"), // 状态码1=启用
DISABLED(0, "禁用"); // 状态码0=禁用
private final int code; // 数据库存储的数字值
private final String desc; // 状态描述
UserStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
// 枚举需提供code的getter,MP会自动映射
public int getCode() {
return code;
}
}
// 2. 实体类直接使用枚举类型
public class User {
private Long id;
private String name;
// 状态字段,类型为枚举
private UserStatus status;
}
// 3. 业务代码中直接使用枚举(无需关心数字值)
userService.lambdaQuery().eq(User::getStatus, UserStatus.ENABLED).list();
09. 多租户隔离:SAAS系统的核心解决方案
核心能力
SAAS系统中,不同租户的数据必须严格隔离,MyBatis-Plus的租户插件能自动在所有查询/更新语句中拼接租户ID条件,无需手动加条件。
// 配置租户拦截器(MybatisPlusConfig)
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 初始化租户拦截器
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
// 1. 获取当前租户ID(实际项目中从ThreadLocal获取)
@Override
public Expression getTenantId() {
// TenantContext:自定义的租户上下文,存储当前租户ID
return new StringValue(TenantContext.getCurrentTenantId());
}
// 2. 租户ID的字段名(数据库表中需有该字段)
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
// 3. 忽略租户隔离的表(如系统配置表)
@Override
public boolean ignoreTable(String tableName) {
return "system_config".equals(tableName);
}
});
// 添加租户拦截器到MP拦截器链
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}
}
10. 代码生成器:一键生成CRUD代码
核心效率
不用再手动编写 Entity、Mapper、Service、Controller 这些重复代码!MyBatis-Plus的代码生成器能根据数据库表结构,一键生成全套代码,节省80%的重复工作量。
public class CodeGenerator {
public static void main(String[] args) {
// 1. 初始化代码生成器
AutoGenerator generator = new AutoGenerator();
// 2. 配置数据源(连接数据库信息)
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("123456");
generator.setDataSource(dataSourceConfig);
// 3. 全局配置(生成代码的基础信息)
GlobalConfig globalConfig = new GlobalConfig();
// 代码输出目录(项目的java目录)
globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
globalConfig.setAuthor("大华"); // 作者名
globalConfig.setOpen(false); // 生成后不自动打开文件夹
globalConfig.setSwagger2(true); // 生成Swagger注解(接口文档)
generator.setGlobalConfig(globalConfig);
// 4. 包配置(代码的包结构)
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.laomao.demo"); // 父包名
packageConfig.setEntity("domain.entity"); // 实体类包名
packageConfig.setMapper("dao.mapper"); // Mapper接口包名
packageConfig.setService("service"); // Service接口包名
packageConfig.setServiceImpl("service.impl"); // Service实现类包名
generator.setPackageInfo(packageConfig);
// 5. 执行生成
generator.execute();
}
}
11. 自定义拦截器:扩展SQL执行逻辑
核心场景
MyBatis-Plus支持自定义 InnerInterceptor,可用于实现数据权限控制、SQL性能监控、字段加解密等场景,是扩展MyBatis-Plus能力的核心方式。
// 示例:慢SQL监控拦截器
@Component
public class SqlLogInterceptor implements InnerInterceptor {
private static final Logger log = LoggerFactory.getLogger(SqlLogInterceptor.class);
// 查询执行前:记录开始时间
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 将开始时间存入BoundSql的附加信息中
boundSql.setAdditionalParameter("startTime", System.currentTimeMillis());
}
// 查询执行后:计算耗时,慢SQL告警
@Override
public void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 获取开始时间
Long startTime = (Long) boundSql.getAdditionalParameter("startTime");
long end = System.currentTimeMillis();
long cost = end - startTime; // 计算执行耗时(毫秒)
// 耗时超过1000ms,触发慢SQL警告
if (cost > 1000) {
log.warn("慢SQL警告: SQL语句={}, 执行耗时={}ms", boundSql.getSql(), cost);
}
}
}
12. 雪花算法:分布式ID生成方案
核心场景
在分布式系统中,数据库自增ID容易导致冲突,而MyBatis-Plus默认集成了雪花算法来生成全局唯一的64位Long型ID,开发者无需再依赖数据库的自增特性。
// 实体类中直接使用Long类型ID,MP默认用雪花算法生成ID
public class User {
// 主键ID(MP会自动生成雪花算法ID)
@TableId(type = IdType.ASSIGN_ID) // 显式指定ID生成策略(可选,默认就是ASSIGN_ID)
private Long id;
private String name;
private Integer status;
}
// 插入数据时,无需设置ID,MP自动生成
User user = new User();
user.setName("张三");
userService.save(user);
// user.getId() 即为雪花算法生成的全局唯一ID
13. 乐观锁:解决高并发数据覆盖问题
核心场景
高并发场景下,多个请求同时更新同一条数据,易出现“后提交覆盖先提交”的问题,MyBatis-Plus的乐观锁通过 version 字段实现无锁并发控制。
// 1. 实体类添加version字段(乐观锁版本号)
public class User {
private Long id;
private String name;
// 乐观锁版本号(必须有,且类型为Integer/Long)
@Version
private Integer version;
}
// 2. 配置乐观锁拦截器(MybatisPlusConfig)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁拦截器
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
// 3. 业务代码中使用(自动基于version更新)
public void updateUserName(Long userId, String newName) {
// 第一步:查询数据(获取当前version)
User user = userService.getById(userId);
// 第二步:修改数据
user.setName(newName);
// 第三步:更新数据(MP会自动拼接WHERE version = 原版本号)
boolean success = userService.updateById(user);
// 若更新失败(version不匹配),需处理并发冲突(如重试/提示用户)
if (!success) {
throw new RuntimeException("数据已被其他用户修改,请刷新后重试");
}
}
14. 结果映射:与MyBatis注解完美兼容
核心场景
MyBatis-Plus完全不干扰MyBatis原有的强大功能,你可以结合 @Result、@ResultMap 等注解实现复杂的结果映射,轻松满足多表联查和VO对象组装的需求。
// Mapper接口中定义联表查询方法(结合MyBatis注解/XML)
public interface UserMapper extends BaseMapper<User> {
// 示例:查询用户+所属部门信息,返回UserVO
@Select("SELECT u.id, u.name, d.name as deptName FROM user u LEFT JOIN dept d ON u.dept_id = d.id WHERE u.id = #{id}")
@Results({
@Result(column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "deptName", property = "deptName") // 映射部门名称
})
UserVO findUserWithDept(Long id);
}
// Service层调用
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public UserVO findUserWithDept(Long id) {
return getBaseMapper().findUserWithDept(id);
}
}
15. 事务控制:保证数据操作的原子性
核心场景
在进行多表或多项操作(如创建用户记录、初始化账户信息、发送通知消息)时,必须保证其原子性。结合Spring的声明式事务,可以有效避免因部分操作失败而导致的数据不一致问题。
@Service
public class UserService extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private AccountService accountService;
@Autowired
private MessageService messageService;
// 声明事务:一旦异常,所有操作回滚
@Transactional(rollbackFor = Exception.class)
public void createUserWithInitData(User user) {
// 1. 保存用户
this.save(user);
// 2. 初始化用户账户
Account account = new Account();
account.setUserId(user.getId());
accountService.save(account);
// 3. 发送欢迎消息
messageService.sendWelcomeMessage(user.getId());
}
}
MyBatis-Plus的强大远不止于此,但掌握以上15个核心技巧,足以覆盖日常开发的绝大多数场景,让你从重复的CRUD中解放出来,把精力聚焦在业务逻辑上。如果你想了解更多关于Java后端开发的深度内容与实战经验,欢迎访问云栈社区进行交流与学习。