当前位置: 首页 > 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.jsqmd.com/news/1084068/

相关文章:

  • Multiwfn波函数分析工具:从编译安装到性能调优的完整指南
  • AI让传统验证码形同虚设,谷歌reCAPTCHA测试手势验证,能否抵御网络欺诈?
  • Sunshine游戏串流:如何构建跨平台自托管游戏中心
  • Cesium 动态围墙(简易版)教程
  • 前OpenAI安全研究VP万字长文扒Scaling Laws:你用的模型可能喂错数据量!
  • 61+技能、92+命令、67+智能体:ECC到底值不值得用?
  • 油层物理——3. 油气藏烃类的相态和汽液平衡
  • 小白 程序员 6 个低门槛 AI 副业,零基础也能月入 2w+
  • Windows 11安卓应用运行深度解析:从零到精通的三段式进阶之旅
  • 5分钟掌握终极浏览器资源嗅探:猫抓Cat-Catch完全免费指南
  • 到底需要多少算力?
  • Scrapy-Redis 分布式爬虫实战——从单机到集群
  • 亲测好用的视频号团购服务商分享
  • 云原生技术21-边缘计算+云原生:让计算力“下沉“到最后一公里,K3s/KubeEdge:在树莓派上跑Kubernetes是什么体验
  • AI医疗时代下的互联网医院APP开发方案解析
  • 360互联网安全大会聚焦智能体威胁,“中国版Mythos”能否破网络安全困局?
  • Apache Dubbo:企业级微服务框架的标杆
  • 基于mac80211_hwsim搭建WiFi模拟测试环境(下)-- 环境搭建与测试
  • LinkSwift:九大网盘直链解析工具,开启高速下载新体验
  • 5分钟掌握《经济研究》LaTeX模板:告别格式困扰的专业解决方案
  • Windows PDF处理终极方案:Poppler预编译包完整指南
  • 轻松打造企业专属应用,低代码开发来助力
  • Cesium 使用Shadertoy教程
  • ASIL-D到底有多难达到?从ISO 26262看车规MCU的研发门槛
  • Windows热键冲突检测工具:Hotkey Detective的完整使用指南
  • ESP32智慧养殖盒开发:4G联网与GPS追踪实战
  • AI语音输入全面进步,BAT入局输入法,能否带来新体验?
  • 记录分布式事务的实现方式和用法(有借助AI)
  • Web开发
  • Cesium 后期处理教程