学完这篇,你能把 MyBatis-Plus 最常用的四件事——条件构造器、分页、乐观锁、逻辑删除——从"会写"变成"写得对",尤其是那些不报错但结果错的坑。
第一步:依赖和全局配置先摆对
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
如果你用的是 Spring Boot 3 + MyBatis-Plus 3.5.9 及以上,starter 要换成 mybatis-plus-spring-boot3-starter,并且额外加一个分页依赖 mybatis-plus-jsqlparser,否则分页插件会直接报 ClassNotFoundException。
application.yml 里把驼峰映射和日志打开,方便看真实 SQL:
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
注意:开发期一定打开 log-impl。条件构造器出问题,九成靠控制台打印的那条 SQL 一眼定位,光看 Java 代码看不出来。
第二步:条件构造器,优先用 Lambda 版
实体上标好注解:
@Data
@TableName("t_order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private String orderNo;
private Integer status;
private BigDecimal amount;
}
查询写法:
LambdaQueryWrapper<Order> w = Wrappers.lambdaQuery(Order.class)
.eq(Order::getUserId, 1001L)
.like(StringUtils.hasText(kw), Order::getOrderNo, kw)
.ge(Order::getAmount, new BigDecimal("100"))
.in(Order::getStatus, Arrays.asList(1, 2))
.orderByDesc(Order::getId);
List<Order> list = orderMapper.selectList(w);
三个关键点:
- 第二个参数传 boolean,
like(条件, 列, 值) 可以按需拼接,不用在 Service 里写一堆 if 拼 wrapper。
- 复杂 or 一定要用括号包起来:
w.eq(Order::getUserId, 1001L)
.and(x -> x.eq(Order::getStatus, 1).or().eq(Order::getStatus, 2));
注意:直接在外层 .or() 会变成 user_id = 1001 OR status = 2,前面所有条件全部失效,这是最典型的"不报错但查错数据"。
- 只更新几个字段用
LambdaUpdateWrapper,别 new 一个实体再 update,避免把 null 之外的值也带下去。
第三步:分页,必须注册拦截器
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor i = new MybatisPlusInterceptor();
PaginationInnerInterceptor page = new PaginationInnerInterceptor(DbType.MYSQL);
page.setMaxLimit(500L); // 单页上限,防止 size=999999 拖垮库
page.setOverflow(false); // 页码超范围时返回空,而不是回到第一页
i.addInnerInterceptor(page);
i.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return i;
}
}
调用:
Page<Order> page = new Page<>(1, 10);
IPage<Order> result = orderMapper.selectPage(page, w);
result.getRecords(); // 当前页数据
result.getTotal(); // 总条数
注意:不注册 PaginationInnerInterceptor 时,selectPage 不会报错,但 total 永远是 0、也没 LIMIT,等于全表查出来再内存截断。上线前务必确认拦截器注入了。
第四步:乐观锁,靠 version 字段
实体加 @Version:
@Version
private Integer version;
更新必须是"先查出对象 → 改字段 → updateById":
Order order = orderMapper.selectById(1L);
order.setStatus(2);
int rows = orderMapper.updateById(order);
if (rows == 0) {
throw new BizException("数据已被他人修改,请刷新重试");
}
生成 SQL 是 ... set status=2, version=version+1 where id=1 and version=3。
注意两个坑:一是 version 字段初始值不能为 null,建表时给 DEFAULT 0;二是用 UpdateWrapper.set() 直接改字段不经过实体时,乐观锁不生效。
第五步:逻辑删除,一次配置全站生效
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
实体上加 @TableLogic private Integer deleted;,之后 deleteById 自动变成 UPDATE,selectList 自动带 deleted = 0。
注意:自定义 XML / @Select 写的 SQL 不会自动加 deleted = 0,必须手写;另外逻辑删除后原唯一索引(如 order_no)仍然占位,再次插入同值会冲突,通常要把唯一索引改成 (order_no, deleted) 联合。
需要真删时,自己写一个 @Delete("delete from t_order where id = #{id}") 的 Mapper 方法。
小结
- 条件构造器优先
LambdaQueryWrapper,动态条件用 boolean 参数,or 条件必须用 and(...) 包起来。
- 分页必须注册
PaginationInnerInterceptor,否则不报错但分页是假的;3.5.9+ 记得加 mybatis-plus-jsqlparser。
- 乐观锁需要
@Version + OptimisticLockerInnerInterceptor + updateById,且判断返回行数。
- 逻辑删除走全局配置 +
@TableLogic,但自定义 SQL 要自己补条件,唯一索引要考虑联合改造。