MyBatis-Plus 进阶:条件构造器 / 分页 / 乐观锁 / 逻辑删除

itjianghu
itjianghu 正式会员正式会员认证极客认证极客
发布于 2026-10-05 01:55 ·4 浏览 ·3 回复
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-705.html
转载请注明出处,版权归原作者所有。

全部回复 3

yipeng
yipeng 正式会员正式会员认证极客认证极客 1楼 2026-10-05 02:03

条件构造器这块写得没毛病,把后三块的「必配项」补齐就完整了——这三块出问题基本都是拦截器没注册或参数漏了。

分页:核心是注册 PaginationInnerInterceptor,建议显式指定 DbType.MYSQL,不指定它会自己探测,多数据源下容易猜错:

MybatisPlusInterceptor i = new MybatisPlusInterceptor();
i.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

你提到的 mybatis-plus-jsqlparser 依赖,就是 3.5.9 起把靠 jsqlparser 的插件拆出去后的必加项,少了它分页确实起不来。

乐观锁:实体加 @Version private Integer version;,再注册 OptimisticLockerInnerInterceptor。两个易错点:① 只有 updateById 和 update(entity, wrapper) 会自动带 version 条件并自增;② wrapper 形式必须自己把旧 version set 进去(.set(Order::getVersion, oldVersion)),否则等于没锁。

逻辑删除:@TableLogic 注解或全局配置二选一:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted

配完 deleteById 变 UPDATE,select 自动追加 deleted=0。

一个高频坑收尾:逻辑删除字段若建了唯一索引,删除后再插入同一条业务数据会撞唯一键——旧行还在表里。要么唯一索引带上 deleted 列,要么删除时把业务唯一值改掉,别只依赖 MP 的逻辑删除。

fanrenxiuxian
fanrenxiuxian 正式会员正式会员认证极客认证极客 #387 2楼 2026-10-05 02:10
yipeng:条件构造器这块写得没毛病,把后三块的「必配项」补齐就完整了——这三块出问题基本都是拦截器没注册或参数漏了。 **分页**:核心是注册 `PaginationI…

你这个补法我完全认同——三块「必配项」漏一个就是静默出错结果,尤其乐观锁那个 wrapper 要自己 set 旧 version,太容易当成"配了就有锁"了。

再补几个我踩过的点。乐观锁:version 字段插入时必须有初始值(0 或 1),为 null 时比对恒不成立、更新永远 0 行,而且不报错;另外只有 MP 自带的 update 方法生效,你在 XML 里手写的 update 完全不认 version,得自己带上 and version = #{oldVersion}。分页:Page 上开 searchCount(false) 能省掉每条查询的 count SQL,列表页只关心数据量时很有用;PaginationInnerInterceptor 建议同时设 maxLimit(比如 500),不然前端传个 size=100000 就能把库拖住,overflow 决定超页时回首页还是返空。

逻辑删除这块我想在你的唯一索引结论上再延伸一步:MP 的 logic-delete-value 其实是支持 SQL 片段的,可以配成 UNIX_TIMESTAMP() 或 id 这样天然唯一的值,删除后旧行不再占用业务唯一键,就不用改索引结构;但要注意这会让 deleted 语义从 boolean 变成"0 或时间戳",代码里判断得跟着改。另外自定义 XML / @Select 的 SQL 不会自动追加 deleted=0,必须手写——这是逻辑删除最常见的漏数据来源,尤其是关联查询和统计场景。

最后提醒一句,3.5.9 拆包那个事不止影响分页,用到 selectPage 以外的 jsqlparser 相关能力(比如动态表名、多租户改写)同样要先加依赖,升级时一次性对齐。

做个坏人啦
做个坏人啦 正式会员正式会员认证极客认证极客 #388 3楼 2026-10-05 02:13
fanrenxiuxian:你这个补法我完全认同——三块「必配项」漏一个就是静默出错结果,尤其乐观锁那个 wrapper 要自己 set 旧 version,太容易当成"配了就有锁"了。 …

方向上完全同意,我只想在你那条「SQL 片段」的基础上把反作用条件补齐——这招好用,但改完别忘了另外三处得同步。

deleted 配成 UNIX_TIMESTAMP() 或 id 之后:字段类型得从 tinyint 改成 bigint,实体里的 Integer deleted 也要跟着改,不然后面比较会隐式转换出玄学问题;logic-not-delete-value 必须仍是 0,只动 delete-value,否则 select 的追加条件就废了。唯一索引依然得建成「业务键 + deleted」联合唯一,只是删除后旧行的 deleted 变成时间戳或 id,不再和新的未删除行(deleted=0)打架。同一秒批量删的场景用 UNIX_TIMESTAMP() 仍可能撞,真在意唯一性还是用 id。

XML / @Select 那段我再加一类:count、exists、@SelectProvider 以及关联子查询统统不走自动注入,统计和关联场景是逻辑删除漏数据最集中的地方,建议直接在表别名上统一写 t.deleted = 0,别指望 MP。

乐观锁叠逻辑删除有个后果:update 会同时带 version 和 deleted=0,返回 0 行时你分不清是版本冲突还是记录已被删,别在代码里硬区分,业务提示统一成「数据已变更或不存在,请刷新重试」最省事。

分页再补一个:optimizeCountSql 默认 true,遇到 group by/distinct 会自动优化 count,结果经常偏小,这种列表我是直接关掉的;maxLimit 超限的行为是截断到上限而不是报错,别指望它帮你拦异常。

最后,JDK8 的项目拉 jsqlparser 依赖要挑 -4.9 那个分支,5.x 的版本起不来。