之前项目用的原生 MyBatis,一个简单的分页查询要写 SQL + PageHelper + 一堆 XML。换了 MyBatis-Plus 之后,大部分 CRUD 一行代码搞定。
基础 CRUD
java
// Entity
@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String email;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@TableLogic
private Integer deleted; // 逻辑删除
}
// Mapper --- 只写一个接口,什么都不用定义
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
// Service
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
}
// 使用
// 插入
userService.save(new User().setName("Alice").setEmail("alice@test.com"));
// 根据 ID 查询
User user = userService.getById(1L);
// 更新
userService.updateById(new User().setId(1L).setName("Alice Updated"));
// 删除(逻辑删除,实际是 UPDATE SET deleted=1)
userService.removeById(1L);
条件构造器
java
// 复杂查询不用写 SQL
List<User> users = userService.list(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.like(User::getName, "alice")
.between(User::getCreatedAt, startDate, endDate)
.orderByDesc(User::getCreatedAt)
.last("LIMIT 10") // 直接拼 SQL(慎用)
);
// 统计数量
long count = userService.count(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.ge(User::getCreatedAt, LocalDate.now().minusDays(7))
);
// 只查部分字段
List<User> partial = userService.list(
new LambdaQueryWrapper<User>()
.select(User::getId, User::getName)
.eq(User::getStatus, 1)
);
LambdaQueryWrapper 用方法引用代替字符串字段名,编译时就能检查字段是否存在,比 QueryWrapper 的字符串安全。
分页
java
// 配置分页插件(一次性)
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
// 使用
Page<User> page = userService.page(
new Page<>(1, 20), // 第 1 页,每页 20 条
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.orderByDesc(User::getCreatedAt)
);
// 结果
page.getRecords() // 当前页数据
page.getTotal() // 总条数
page.getPages() // 总页数
page.getCurrent() // 当前页码
不用 PageHelper,不用写 LIMIT,MP 自动帮你拼。
自动填充
java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createdAt", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime::now, LocalDateTime.class);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime::now, LocalDateTime.class);
}
}
插入和更新的时候自动填充时间字段,不用在每个 Service 里手动 setCreatedAt。
逻辑删除
java
@TableLogic
private Integer deleted; // 0=未删除, 1=已删除
加了 @TableLogic 之后,removeById 变成 UPDATE SET deleted=1,select 自动加 WHERE deleted=0。数据不真正删除,但可以恢复。
代码生成器
java
// 一键生成 Entity + Mapper + Service + Controller
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mydb", "root", "password")
.globalConfig(builder -> builder
.author("zhangsan")
.outputDir("/src/main/java")
)
.packageConfig(builder -> builder
.parent("com.example")
.moduleName("system")
)
.strategyConfig(builder -> builder
.addInclude("sys_user", "sys_role") // 指定表
.entityBuilder().enableLombok()
.controllerBuilder().enableRestStyle()
.serviceBuilder().formatServiceFileName("%sService")
)
.execute();
新建表之后跑一次代码生成器,CRUD 代码直接就有了。
注意事项
- 复杂 SQL 还是写 XML:多表关联、复杂子查询,MP 的 Wrapper 表达力有限,该写 SQL 就写
- Wrapper 的链式调用注意性能:特别长的条件链在极端情况下有性能开销,但一般可以忽略
- 不要滥用
last():直接拼 SQL 有注入风险,参数必须自己校验
总结
MyBatis-Plus 的价值:单表 CRUD 零 SQL、分页自动化、条件构造器类型安全、自动填充省重复代码、逻辑删除一行注解。复杂查询还是写原生 SQL,两者互补。