三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

MyBatis-Plus 进阶实战|告别只会CRUD!搞定企业级高频场景

MyBatis-Plus 进阶实战|告别只会CRUD!搞定企业级高频场景

很多小伙伴开发时都会遇到一堆问题:

复杂多条件模糊查询怎么写?并发更新数据错乱怎么办?逻辑删除后统计数据不准?批量新增效率低?自定义SQL怎么和MP混用?

只会基础CRUD,只能应付简单demo。真正的企业项目,全靠MP高阶功能撑场面。

今天这篇进阶干货,专门解决工作中90%的MP实战难题,看完直接适配生产环境开发!


一、先划重点:MP进阶核心思维

很多人用MP有个误区:把MP当成“只会生成SQL的工具”。

实际上企业级MP核心用法就两点:

1)简单业务:全程用MP条件构造器,零SQL开发

2)复杂业务/多表联查:MP与原生SQL混用,灵活不冲突

下面全是生产级高频用法,直接复制就能用!

二、LambdaQueryWrapper 高阶查询(避坑版)

基础的等值、范围查询大家都会,工作中最常用、最容易出错的是动态多条件拼接。

场景:后台列表查询,用户名、年龄、邮箱都是非必传参数,传了才查询,不传不生效。

1)新手错误写法

无脑拼接条件,容易出现空字符串、null导致查询数据异常:

// 极易出Bug!空参数也会拼接条件

wrapper.like(User::getUsername, username);

wrapper.gt(User::getAge, age);

2)进阶优雅写法(带条件判断)

MP自带 condition 参数,满足条件才拼接SQL,超级简洁:

public List<User> getUserList(String username, Integer age, String email){

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

// 仅当用户名不为空时,执行模糊查询

wrapper.like(StringUtils.hasText(username), User::getUsername, username)

// 仅当年龄不为null时,执行大于查询

.gt(age != null, User::getAge, age)

// 仅当邮箱不为空时,精准匹配

.eq(StringUtils.hasText(email), User::getEmail, email);

return userMapper.selectList(wrapper);

}

核心技巧:所有条件构造方法的第一个参数,都可以加布尔判断,彻底干掉一堆 if-else,代码干净又安全!

拓展:排序、去重、指定字段查询

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

// 只查询指定字段,避免查询全部字段浪费性能

wrapper.select(User::getId, User::getUsername, User::getAge);

// 年龄倒序,创建时间正序

wrapper.orderByDesc(User::getAge)

.orderByAsc(User::getCreateTime);

// 去重查询

wrapper.distinct(true);

三、批量操作:告别for循环单条保存

新手开发批量新增/修改,总喜欢用 for循环 + insert/update,数据库频繁交互,性能极差。

MP内置ServiceImpl,自带高效批量方法,底层是批量预处理SQL,性能碾压循环操作。

1. 先改造Service层(进阶必备)

基础Mapper只适合单条操作,批量操作必须用IService:

// Service接口

public interface UserService extends IService<User> {}

// Service实现类

@Service

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}

2. 高效批量新增/修改

@Autowired

private UserService userService;

// 批量新增

@Test

void batchSave(){

List<User> userList = new ArrayList<>();

// 批量封装数据...

userService.saveBatch(userList);

}

// 批量更新(根据ID)

@Test

void batchUpdate(){

List<User> userList = new ArrayList<>();

userService.updateBatchById(userList);

}

小细节:批量方法支持设置批次大小,saveBatch(list, 1000),适配大数据量场景,避免一次性数据过多报错。

四、乐观锁:解决并发更新数据丢失问题

生产高频Bug:多人同时修改同一条数据,后提交的数据会覆盖先提交的数据,导致数据丢失。

MP自带乐观锁插件,无需手写锁逻辑,完美解决并发更新问题。

1. 数据库加版本字段

新增字段:version int default 1 comment '版本号'

2. 实体类添加注解

@Version // 乐观锁版本号注解

private Integer version;

3. 开启乐观锁插件

@Bean

public MybatisPlusInterceptor mybatisPlusInterceptor(){

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

// 添加乐观锁插件

interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

return interceptor;

}

实现原理:每次更新会校验版本号,更新成功版本号+1;如果版本号不匹配(被他人修改过),更新失败,避免数据覆盖。

五、MP + 自定义SQL 完美混用

MP不是万能的,多表联查、复杂统计、子查询,还是原生SQL最顺手。

重点:MP可以和原生MyBatis完全混用,互不冲突!

1. Mapper自定义方法

// 自定义多表查询方法

List<UserVO> getUserAndDeptList();

2. 对应XML写原生SQL

正常写联查、统计SQL即可,MP的分页、日志、插件依旧生效,完全兼容。

六、生产避坑:逻辑删除的隐形坑

上篇讲过逻辑删除,但很多人上线后出问题:统计表数据不准、自定义SQL查不到已删除数据。

坑点:

开启逻辑删除全局配置后,MP会自动在所有查询拼接 where deleted=0。

解决方案:

需要查询全部数据(包含已删除)时,手动关闭当前查询的逻辑删除:

// 忽略逻辑删除,查询所有数据

QueryWrapper<User> wrapper = new QueryWrapper<>().setSqlSelect("*");

wrapper.last("limit 100");

// 核心:关闭本次查询逻辑删除

userMapper.selectList(wrapper.ignoreLogicDelete());

七、高级查询:分页+条件+排序 组合实战

给大家一套项目通用的后台列表分页模板,直接复用:

public Page<User> pageUser(Integer pageNum, Integer pageSize, String username, Integer age) {

// 分页参数

Page<User> page = new Page<>(pageNum, pageSize);

// 动态条件

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

wrapper.like(StringUtils.hasText(username), User::getUsername, username)

.gt(age != null, User::getAge, age)

.orderByDesc(User::getCreateTime);

// 分页查询

return userMapper.selectPage(page, wrapper);

}

← 返回列表