当前位置: 首页 > news >正文

MyBatis-Plus 高级用法实战——分页、条件构造器、乐观锁、逻辑删除

MyBatis-Plus 是 MyBatis 的增强工具,在国内企业级项目中几乎是标配。上一篇讲完了基础 CRUD,这一篇把高频高级用法一次性说清楚。

一、分页查询

MP 的分页插件配置很简单,但配置错了会不生效。

1. 配置分页插件

@ConfigurationpublicclassMyBatisPlusConfig{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();// 添加分页拦截器(设置数据库类型)interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}}

没有这个配置,分页不会生效,Page 对象会查出所有数据。

2. 使用 Page 对象

// 查第一页,每页 10 条Page<User>page=newPage<>(1,10);// 条件查询 + 分页LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.like(User::getName,"张");wrapper.orderByDesc(User::getCreateTime);// 执行分页查询Page<User>result=userMapper.selectPage(page,wrapper);// 从 result 中获取分页信息System.out.println("总记录数: "+result.getTotal());System.out.println("总页数: "+result.getPages());System.out.println("当前页: "+result.getCurrent());System.out.println("每页大小: "+result.getSize());System.out.println("是否有下一页: "+result.hasNext());System.out.println("数据列表: "+result.getRecords());

3. Service 层的分页

publicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{publicPage<User>pageUsers(intpageNum,intpageSize,Stringkeyword){Page<User>page=newPage<>(pageNum,pageSize);LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(keyword),User::getName,keyword);wrapper.eq(User::getStatus,1);wrapper.orderByDesc(User::getCreateTime);returnthis.page(page,wrapper);// 或者 baseMapper.selectPage(page, wrapper)}}

4. 分页返回 DTO(自定义结果集)

// 实体类是 User,但只需要部分字段Page<UserVO>page=newPage<>(1,10);Page<UserVO>result=userMapper.selectUserPage(page,"张");// Mapper.xml// <select id="selectUserPage" resultType="com.zhang.vo.UserVO">// SELECT id, username, email, phone FROM user WHERE username LIKE CONCAT('%', #{keyword}, '%')// </select>

Page 的泛型可以和实体类不一致,泛型仅决定返回的 records 类型。

二、Lambda 条件构造器

MP 最强大的功能之一,SQL 条件不用手写。

常用条件方法

LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.eq(User::getStatus,1);// 等于wrapper.ne(User::getStatus,0);// 不等于wrapper.gt(User::getAge,18);// 大于wrapper.ge(User::getAge,18);// 大于等于wrapper.lt(User::getAge,60);// 小于wrapper.le(User::getAge,60);// 小于等于wrapper.like(User::getName,"张");// 模糊匹配 %张%wrapper.notLike(User::getName,"测试");// 不包含wrapper.likeLeft(User::getName,"张");// 左模糊 %张wrapper.likeRight(User::getName,"张");// 右模糊 张%wrapper.in(User::getStatus,1,2,3);// IN 查询wrapper.notIn(User::getStatus,0);// NOT INwrapper.between(User::getCreateTime,start,end);// BETWEENwrapper.notBetween(User::getCreateTime,s,e);// NOT BETWEENwrapper.isNull(User::getEmail);// IS NULLwrapper.isNotNull(User::getEmail);// IS NOT NULLwrapper.orderByAsc(User::getSort);// 升序wrapper.orderByDesc(User::getCreateTime);// 降序wrapper.last("LIMIT 1");// 拼接 SQL 片段wrapper.exists("SELECT 1 FROM ...");// EXISTS 子查询

条件拼接(带 if 判断)

LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();// 第一个参数是 condition,只有为 true 时才拼接此条件wrapper.like(StringUtils.isNotBlank(name),User::getName,name);wrapper.eq(age!=null,User::getAge,age);wrapper.ge(startTime!=null,User::getCreateTime,startTime);wrapper.le(endTime!=null,User::getCreateTime,endTime);

这是最常用的写法,参数为空时自动忽略该条件,不用写一堆 if 在外面。

and / or 嵌套

// WHERE age > 18 AND (name LIKE '张%' OR name LIKE '王%')wrapper.gt(User::getAge,18);wrapper.and(w->w.like(User::getName,"张").or().like(User::getName,"王"));

只查指定字段

// 只查 id、name、email,不查全部字段wrapper.select(User::getId,User::getName,User::getEmail);// 或排除某些字段wrapper.select(User.class,info->!info.getColumn().equals("password")// 不查密码);

三、乐观锁——防并发修改

适合"读多写少"的场景,比如秒杀库存扣减、文章点赞数更新。

1. 配置乐观锁插件

@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());// 乐观锁returninterceptor;}

2. 实体类加 @Version 注解

@Entity@TableName("product")publicclassProduct{@TableIdprivateLongid;privateStringname;privateBigDecimalprice;privateIntegerstock;@Version// 乐观锁版本号privateIntegerversion;}

数据库加一个version字段,默认值为 0。

3. 更新时自动检测

// 先查询(获取当前 version)Productproduct=productMapper.selectById(1L);System.out.println("当前版本: "+product.getVersion());// 0// 修改数据product.setStock(product.getStock()-1);// 更新时 MP 自动拼接 WHERE version = 0// UPDATE product SET stock = ?, version = version + 1 WHERE id = ? AND version = 0introws=productMapper.updateById(product);if(rows==0){System.out.println("数据已被别人修改,请重试");}

原理:更新时SET version = version + 1,条件是WHERE version = 旧值。如果别人先改了,版本号变了,当前更新影响行数为 0,说明发生并发冲突。

四、逻辑删除——数据恢复留后路

业务上一般不做物理删除,而是标记删除。

1. 配置

mybatis-plus:global-config:db-config:logic-delete-field:is_deleted# 全局逻辑删除字段logic-delete-value:1# 已删除logic-not-delete-value:0# 未删除

2. 实体类

@TableName("user")publicclassUser{@TableIdprivateLongid;privateStringname;@TableLogic// 逻辑删除注解privateIntegerisDeleted;}

3. 效果

// 执行 delete 时,变成 UPDATEuserMapper.deleteById(1L);// 实际 SQL: UPDATE user SET is_deleted = 1 WHERE id = 1 AND is_deleted = 0// 查询时自动拼接条件userMapper.selectList(null);// 实际 SQL: SELECT * FROM user WHERE is_deleted = 0// 如果想查已删除的userMapper.selectList(newLambdaQueryWrapper<User>().eq(User::getIsDeleted,1));

注意:逻辑删除会让唯一索引失效——比如用户表用手机号做唯一索引,A 用户注销后(逻辑删除),B 用户注册同手机号会冲突。解决方案:联合唯一索引(phone+is_deleted)。

五、自动填充——createTime / updateTime 不用手动 set

@ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{@OverridepublicvoidinsertFill(MetaObjectmetaObject){this.strictInsertFill(metaObject,"createTime",LocalDateTime.class,LocalDateTime.now());this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}@OverridepublicvoidupdateFill(MetaObjectmetaObject){this.strictUpdateFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}}

实体类上加注解:

@TableField(fill=FieldFill.INSERT)privateLocalDateTimecreateTime;@TableField(fill=FieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;

以后insertupdate时,这两个字段自动填充,不用写冗余代码。

六、批量操作

// 批量插入(JDBC 自动拼接成一条 INSERT 多值语句)userService.saveBatch(userList);// 默认每次 1000 条userService.saveBatch(userList,500);// 自定义批次大小// 批量更新userService.updateBatchById(userList);// 批量删除userService.removeByIds(Arrays.asList(1L,2L,3L));

性能提示:saveBatch底层是 for 循环每批次提交一次,不是真正的批量 insert,数据量大时建议自己写 XML 的 foreach。

七、实战:一个完整的 Service

@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{/** * 分页查询用户(带条件) */@OverridepublicPage<User>queryUserPage(UserQueryDTOdto){Page<User>page=newPage<>(dto.getPageNum(),dto.getPageSize());LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(dto.getName()),User::getName,dto.getName());wrapper.eq(dto.getStatus()!=null,User::getStatus,dto.getStatus());wrapper.between(dto.getStartTime()!=null&&dto.getEndTime()!=null,User::getCreateTime,dto.getStartTime(),dto.getEndTime());wrapper.orderByDesc(User::getCreateTime);returnthis.page(page,wrapper);}/** * 更新用户(带乐观锁重试) */@Override@Retryable(value=OptimisticLockException.class,maxAttempts=3)publicbooleanupdateWithRetry(Useruser){returnthis.updateById(user);}}

总结

功能核心注解/类常见用途
分页PaginationInnerInterceptor列表查询
条件构造LambdaQueryWrapper动态 SQL 查询
乐观锁@Version并发更新
逻辑删除@TableLogic数据恢复
自动填充MetaObjectHandler时间戳、操作人
批量操作saveBatch/updateBatchById大批量数据

💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

http://www.gsyq.cn/news/1596015.html

相关文章:

  • Sunshine游戏串流:如何构建跨平台自托管游戏中心
  • 小白 程序员 6 个低门槛 AI 副业,零基础也能月入 2w+
  • 如何快速解决联发科设备变砖问题:终极修复方案
  • Scrapy-Redis 分布式爬虫实战——从单机到集群
  • Apache Dubbo:企业级微服务框架的标杆
  • 360互联网安全大会聚焦智能体威胁,“中国版Mythos”能否破网络安全困局?
  • LinkSwift:九大网盘直链解析工具,开启高速下载新体验
  • Windows PDF处理终极方案:Poppler预编译包完整指南
  • 设计数据密集型应用第2版:2025-2026出版新书的《人月神话》引用(4)
  • 夏天总疲惫乏力、容易累、爱出汗的人,常喝这杯茶,养出元气满满好状态~
  • 终极无广告体验:SpotifyPremium桌面版完整配置指南
  • ts3380,G3000,ix6780,MG3640,ix6700,ix6800,G5080,TS8380,ts8220报错5B00,P07,E08,1700,5b02废墨垫清零,亲测可用
  • 鸿蒙语音识别的 Flutter ↔ ArkTS 完整调用链:权限申请、引擎生命周期与结果回传的时序问题
  • 进销存软件不一定贵,但要看这几点
  • 告别链接失效烦恼:百度网盘秒传脚本完全指南
  • 中医药现代化研究,国自然申请书怎么写才能中?
  • 一台高配置图形工作站带10人SolidWorks画图的实施方案是怎样的
  • 用你自己的签名,打你自己
  • 微信会话存档亿级数据处理:基于 RSA 混合解密与 Flink 的流式架构实战
  • C#工业相机开发从零到一:图像采集与显示的工程化实战
  • 从CTF实战解析SQL注入:绕过过滤与联合查询攻防
  • Python+Selenium自动化测试:Chrome Driver版本管理全流程实现
  • 天行健与优胜劣汰:两种文明范式的哲学比较及其现代启示
  • LSR包胶技术深度解析:金属包胶、塑料包胶到底怎么做?
  • OpenAI 9 个月自研芯片 Jalapeño,推理成本砍半,ChatGPT 体验将大升级!
  • 天河应用大讲堂 | 基于人工智能的天气预报技术发展趋势
  • 打通企微接口,构建适配 GEO 检索规则的结构化素材库
  • 从安装到调优,Strix Halo 本地大模型一周使用实录
  • C++跨平台(一):开发概述与策略选择
  • 合同系统智能化,让企业合同管理快人一步!