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

发布时间:2026/6/26 22:32:34
MyBatis-Plus 高级用法实战——分页、条件构造器、乐观锁、逻辑删除 MyBatis-Plus 是 MyBatis 的增强工具在国内企业级项目中几乎是标配。上一篇讲完了基础 CRUD这一篇把高频高级用法一次性说清楚。一、分页查询MP 的分页插件配置很简单但配置错了会不生效。1. 配置分页插件ConfigurationpublicclassMyBatisPlusConfig{BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();// 添加分页拦截器设置数据库类型interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}}没有这个配置分页不会生效Page 对象会查出所有数据。2. 使用 Page 对象// 查第一页每页 10 条PageUserpagenewPage(1,10);// 条件查询 分页LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();wrapper.like(User::getName,张);wrapper.orderByDesc(User::getCreateTime);// 执行分页查询PageUserresultuserMapper.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 层的分页publicclassUserServiceImplextendsServiceImplUserMapper,UserimplementsUserService{publicPageUserpageUsers(intpageNum,intpageSize,Stringkeyword){PageUserpagenewPage(pageNum,pageSize);LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();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但只需要部分字段PageUserVOpagenewPage(1,10);PageUserVOresultuserMapper.selectUserPage(page,张);// Mapper.xml// select idselectUserPage resultTypecom.zhang.vo.UserVO// SELECT id, username, email, phone FROM user WHERE username LIKE CONCAT(%, #{keyword}, %)// /selectPage 的泛型可以和实体类不一致泛型仅决定返回的 records 类型。二、Lambda 条件构造器MP 最强大的功能之一SQL 条件不用手写。常用条件方法LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();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 判断LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();// 第一个参数是 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(){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());// 乐观锁returninterceptor;}2. 实体类加 Version 注解EntityTableName(product)publicclassProduct{TableIdprivateLongid;privateStringname;privateBigDecimalprice;privateIntegerstock;Version// 乐观锁版本号privateIntegerversion;}数据库加一个version字段默认值为 0。3. 更新时自动检测// 先查询获取当前 versionProductproductproductMapper.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 0introwsproductMapper.updateById(product);if(rows0){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(newLambdaQueryWrapperUser().eq(User::getIsDeleted,1));注意逻辑删除会让唯一索引失效——比如用户表用手机号做唯一索引A 用户注销后逻辑删除B 用户注册同手机号会冲突。解决方案联合唯一索引phoneis_deleted。五、自动填充——createTime / updateTime 不用手动 setComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{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(fillFieldFill.INSERT)privateLocalDateTimecreateTime;TableField(fillFieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;以后insert和update时这两个字段自动填充不用写冗余代码。六、批量操作// 批量插入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。七、实战一个完整的 ServiceServicepublicclassUserServiceImplextendsServiceImplUserMapper,UserimplementsUserService{/** * 分页查询用户带条件 */OverridepublicPageUserqueryUserPage(UserQueryDTOdto){PageUserpagenewPage(dto.getPageNum(),dto.getPageSize());LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();wrapper.like(StringUtils.isNotBlank(dto.getName()),User::getName,dto.getName());wrapper.eq(dto.getStatus()!null,User::getStatus,dto.getStatus());wrapper.between(dto.getStartTime()!nulldto.getEndTime()!null,User::getCreateTime,dto.getStartTime(),dto.getEndTime());wrapper.orderByDesc(User::getCreateTime);returnthis.page(page,wrapper);}/** * 更新用户带乐观锁重试 */OverrideRetryable(valueOptimisticLockException.class,maxAttempts3)publicbooleanupdateWithRetry(Useruser){returnthis.updateById(user);}}总结功能核心注解/类常见用途分页PaginationInnerInterceptor列表查询条件构造LambdaQueryWrapper动态 SQL 查询乐观锁Version并发更新逻辑删除TableLogic数据恢复自动填充MetaObjectHandler时间戳、操作人批量操作saveBatch/updateBatchById大批量数据 觉得有用的话点赞 关注【张老师技术栈】吧每周更新 Java/Python/爬虫 实战干货不让你白来。