MyBatis-Plus(MP)的核心价值是 “增强 MyBatis 但不改变其本质”,通过封装高频操作、提供便捷工具,大幅减少重复编码。以下是其最核心、最常用的功能,结合实际用法和场景说明,让你快速抓住 MP 的精髓:
MP 最核心的功能之一,通过继承 BaseMapper(Mapper 层)或 IService(Service 层),零 XML/注解实现 80% 的 CRUD 操作,无需手动编写 insert/delete/update/select 基础 SQL。
所有 Mapper 接口只需继承 BaseMapper<T>,即可直接使用内置的 CRUD 方法,无需编写任何接口方法。
方法 | 功能描述 | 示例代码 |
|---|---|---|
insert(T entity) | 新增一条记录 | userMapper.insert(new User("张三", 20)); |
deleteById(Serializable id) | 根据主键删除 | userMapper.deleteById(1L); |
updateById(T entity) | 根据主键更新(非 null 字段) | User user = new User(); user.setId(1L); user.setAge(21); userMapper.updateById(user); |
selectById(Serializable id) | 根据主键查询 | User user = userMapper.selectById(1L); |
selectList(Wrapper<T> queryWrapper) | 条件查询列表 | 配合条件构造器使用(见下文) |
selectPage(Page<T> page, Wrapper<T> queryWrapper) | 分页查询 | 配合分页插件使用(见下文) |
// Mapper 接口(无需写任何方法)
public interface UserMapper extends BaseMapper<User> {
}
// 调用示例(Service 层)
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void addUser(User user) {
userMapper.insert(user); // 直接使用 BaseMapper 方法
}
}Service 层推荐继承 IService<T> + 实现类继承 ServiceImpl<Mapper, T>,在 BaseMapper 基础上增加了 批量操作、逻辑判断 等更贴合业务的方法,比直接用 BaseMapper 更优雅。
方法 | 功能描述 | 示例代码 |
|---|---|---|
save(T entity) | 新增(同 insert,语义更清晰) | userService.save(user); |
removeById(Serializable id) | 逻辑删除(配合全局配置) | userService.removeById(1L); |
updateById(T entity) | 更新(同 BaseMapper) | userService.updateById(user); |
getById(Serializable id) | 查询单条(同 selectById) | User user = userService.getById(1L); |
list(Wrapper<T> queryWrapper) | 条件查询列表 | List<User> list = userService.list(queryWrapper); |
page(Page<T> page, Wrapper<T> queryWrapper) | 分页查询 | IPage<User> page = userService.page(new Page<>(1,10), queryWrapper); |
saveBatch(Collection<T> entityList, int batchSize) | 批量新增 | userService.saveBatch(userList, 500); |
updateBatchById(Collection<T> entityList, int batchSize) | 批量更新 | userService.updateBatchById(userList, 300); |
count(Wrapper<T> queryWrapper) | 条件统计总数 | long count = userService.count(queryWrapper); |
// Service 接口
public interface UserService extends IService<User> {
}
// Service 实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 无需重写基础 CRUD,直接使用父类方法
}
// 调用示例
@Controller
public class UserController {
@Autowired
private UserService userService;
public IPage<User> getUserPage(Integer pageNum, Integer pageSize) {
// 分页查询:第1页,每页10条
return userService.page(new Page<>(pageNum, pageSize), null);
}
}MP 最强大的功能之一,动态拼接 SQL 条件,无需手动写 WHERE 子句,支持所有 SQL 条件(等于、模糊查询、范围查询等),且提供 Lambda 版本(类型安全,避免字段名写错)。
User::getUserName),无需硬编码字段名(如 "user_name"),减少字段修改导致的 Bug;方法 | 功能描述 | 示例代码(查询年龄>18且用户名含“张”的用户) |
|---|---|---|
eq | 等于(=) | eq(User::getId, 1L) |
ne | 不等于(!=) | ne(User::getStatus, 0) |
gt | 大于(>) | gt(User::getAge, 18) |
ge | 大于等于(>=) | ge(User::getAge, 18) |
lt | 小于(<) | lt(User::getAge, 30) |
le | 小于等于(<=) | le(User::getAge, 30) |
like | 模糊查询(LIKE %值%) | like(User::getUserName, "张") |
likeLeft | 左模糊(LIKE %值) | likeLeft(User::getUserName, "张") |
likeRight | 右模糊(LIKE 值%) | likeRight(User::getUserName, "张") |
in | IN 查询 | in(User::getId, 1L, 2L, 3L) |
notIn | NOT IN 查询 | notIn(User::getId, 4L, 5L) |
isNull | 字段为 NULL | isNull(User::getPhone) |
isNotNull | 字段不为 NULL | isNotNull(User::getPhone) |
orderByAsc | 升序排序 | orderByAsc(User::getCreateTime) |
orderByDesc | 降序排序 | orderByDesc(User::getCreateTime) |
and/or | 逻辑与/或(嵌套条件) | and(w -> w.gt(User::getAge,18).like(User::getUserName,"张")) |
// 场景:根据前端传入的参数(可能为 null)动态筛选
public List<User> queryUser(String userName, Integer age, Integer status) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
// 只有 userName 不为 null 且不为空,才拼接模糊查询
.like(StringUtils.hasText(userName), User::getUserName, userName)
// 只有 age 不为 null,才拼接 age >= age
.ge(age != null, User::getAge, age)
// 只有 status 不为 null,才拼接 status = status
.eq(status != null, User::getStatus, status)
// 固定条件:未删除(逻辑删除)
.eq(User::getIsDeleted, 0)
// 排序:按创建时间降序
.orderByDesc(User::getCreateTime);
return userService.list(queryWrapper);
}@Data 可自动生成);QueryWrapper<User>().like("user_name", "张"));and/or 嵌套:提高代码可读性,例如:// 条件:(age > 18 且 user_name 含"张") 或 (age < 10 且 user_name 含"李")
queryWrapper.and(w -> w.gt(User::getAge,18).like(User::getUserName,"张"))
.or(w -> w.lt(User::getAge,10).like(User::getUserName,"李"));MP 内置分页插件,无需手动编写分页 SQL(无需 LIMIT 关键字),只需传入 Page 对象,即可自动实现分页查询,支持总数统计、分页参数自动计算。
IPage 对象,包含总记录数、总页数、当前页数据等。// 场景:分页查询年龄>18的用户,第1页,每页10条
public IPage<User> getUserPage(Integer pageNum, Integer pageSize) {
// 1. 构建分页对象(pageNum:页码,pageSize:每页条数)
Page<User> page = new Page<>(pageNum, pageSize);
// 2. 构建查询条件
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
.gt(User::getAge, 18)
.eq(User::getIsDeleted, 0);
// 3. 分页查询(Service 层 page 方法)
IPage<User> userPage = userService.page(page, queryWrapper);
// 4. 分页结果解析
long total = userPage.getTotal(); // 总记录数
long pages = userPage.getPages(); // 总页数
List<User> records = userPage.getRecords(); // 当前页数据
return userPage;
}即使是多表联查的自定义 SQL,MP 也能自动分页,只需在 Mapper 方法中传入 Page 对象作为第一个参数:
// Mapper 接口
public interface UserMapper extends BaseMapper<User> {
// 自定义分页:多表联查(用户+部门)
IPage<UserVO> selectUserWithDept(Page<User> page, @Param("query") UserQueryDTO query);
}
// XML 文件(无需写 LIMIT,MP 自动拼接)
<select id="selectUserWithDept" resultType="com.example.vo.UserVO">
SELECT u.id, u.user_name, d.dept_name
FROM t_user u
LEFT JOIN t_dept d ON u.dept_id = d.id
WHERE u.is_deleted = 0
<if test="query.userName != null">
AND u.user_name LIKE CONCAT('%', #{query.userName}, '%')
</if>
</select>
// 调用示例
public IPage<UserVO> queryUserWithDept(Integer pageNum, Integer pageSize, UserQueryDTO query) {
return userMapper.selectUserWithDept(new Page<>(pageNum, pageSize), query);
}COUNT(*) 会影响性能,可关闭总数统计:Page<User> page = new Page<>(pageNum, pageSize, false); // 第三个参数:是否查询总数(默认 true)ORDER BY 字段和查询条件字段必须建索引,避免全表扫描导致分页卡顿。MP 支持逻辑删除(假删除),通过字段标记数据是否删除(如 is_deleted),而非直接删除数据库记录,保障数据可恢复性。
is_deleted = 未删除值,无需手动写条件;removeById 自动改为更新 is_deleted = 已删除值,而非 DELETE 语句。mybatis-plus:
global-config:
db-config:
logic-delete-field: isDeleted # 全局逻辑删除字段名(实体类属性名)
logic-delete-value: 1 # 已删除值(数据库存储值)
logic-not-delete-value: 0 # 未删除值(数据库存储值)@Data
@TableName("t_user")
public class User {
// 逻辑删除字段(全局配置后,@TableLogic 可省略,若字段名与全局配置不一致需显式指定)
@TableLogic
private Integer isDeleted;
}userService.removeById(1L) 自动执行 UPDATE t_user SET is_deleted = 1 WHERE id = 1 AND is_deleted = 0;userService.getById(1L) 自动执行 SELECT * FROM t_user WHERE id = 1 AND is_deleted = 0;AND is_deleted = 0;tinyint(0/1),避免用 boolean(兼容性差)。对于 create_time(创建时间)、update_time(更新时间)等通用字段,MP 支持自动填充,无需手动设置值,减少重复编码。
@Data
@TableName("t_user")
public class User {
// 插入时填充(仅新增时设置)
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 插入+更新时填充(新增和修改时都设置)
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}@Component // 必须交给 Spring 管理
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时填充
@Override
public void insertFill(MetaObject metaObject) {
// 严格填充:只有字段为 null 时才填充(避免覆盖手动设置的值)
strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
// 更新时填充
@Override
public void updateFill(MetaObject metaObject) {
strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}userService.save(user) 自动设置 createTime 和 updateTime 为当前时间;userService.updateById(user) 自动更新 updateTime 为当前时间;user.setCreateTime(LocalDateTime.now()),减少重复代码。MP 提供多种主键生成策略,适配单库、分布式等不同场景,无需手动管理主键值。
策略类型 | 适用场景 | 配置方式 |
|---|---|---|
AUTO | MySQL 自增字段(单库场景) | @TableId(type = IdType.AUTO) |
ASSIGN_ID | 分布式场景(雪花算法) | @TableId(type = IdType.ASSIGN_ID) |
ASSIGN_UUID | 分布式场景(UUID 字符串) | @TableId(type = IdType.ASSIGN_UUID) |
INPUT | 手动输入主键 | @TableId(type = IdType.INPUT) |
在 application.yml 中全局配置主键策略,避免每个实体类重复注解:
mybatis-plus:
global-config:
db-config:
id-type: AUTO # 单库场景(MySQL 自增)
# id-type: ASSIGN_ID # 分布式场景(雪花算法,生成 19 位 Long 型 ID)AUTO(需数据库字段设置自增),简单高效;ASSIGN_ID(MP 内置雪花算法,无需数据库配置,生成唯一 ID),避免 ID 冲突;ASSIGN_UUID(生成无中划线的 UUID 字符串,如 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed)。在并发场景下(如多用户同时编辑同一条数据),乐观锁通过版本号机制避免数据覆盖,MP 内置乐观锁插件,配置简单。
version 字段(版本号),数据库对应字段设为 int 类型,默认值 0;version = 原版本号,并设置 version = 原版本号 + 1;@Data
@TableName("t_user")
public class User {
// 乐观锁版本号字段(必须加 @Version 注解)
@Version
private Integer version; // 数据库字段默认值 0
}@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}// 并发更新用户信息
public boolean updateUser(UserUpdateDTO dto) {
// 1. 查询当前用户(获取最新版本号)
User user = userService.getById(dto.getId());
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 2. 校验版本号(前端需传入当前版本号,避免并发覆盖)
if (!user.getVersion().equals(dto.getVersion())) {
throw new RuntimeException("数据已被修改,请刷新后重试");
}
// 3. 更新数据(MP 自动拼接 version 条件)
BeanUtils.copyProperties(dto, user);
return userService.updateById(user);
}更新时自动执行:
UPDATE t_user
SET user_name = '李四', version = 1
WHERE id = 1 AND version = 0;version = 0 已变为 1),则受影响行数为 0,更新失败。IService(Service 层)+ BaseMapper(Mapper 层),零 XML 实现;LambdaQueryWrapper,类型安全,动态拼接;Page + IPage,无需手动写 LIMIT;ASSIGN_ID 主键策略,避免 ID 冲突。掌握以上核心功能,可覆盖 90% 以上的业务场景,大幅减少重复编码,让开发精力聚焦在业务逻辑上。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。