1. MyBatis-Plus 内置方法全景解析
作为MyBatis的增强工具,MyBatis-Plus的核心价值在于它提供了一套开箱即用的CRUD操作方法。这些内置方法主要分布在两个关键接口中:BaseMapper和IService。BaseMapper提供了最基础的数据库操作方法,而IService在其基础上进行了业务逻辑的扩展。
在实际项目中,我发现很多开发者虽然每天都在使用这些方法,但对它们的完整能力和使用边界并不完全清楚。比如,saveBatch()方法在MySQL的InnoDB引擎下默认是单条提交,需要特别配置才能实现真正的批量插入。这种细节往往决定了系统性能的优劣。
重要提示:MyBatis-Plus 3.5.0版本后对内置方法进行了多项优化,特别是批量操作方法性能提升显著,建议使用最新稳定版。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. BaseMapper 核心方法详解
2.1 基础CRUD方法组
BaseMapper接口提供了最基础的数据库操作方法,这些方法直接映射到XML中的SQL语句。以下是几个最常用的方法示例:
java复制// 插入记录
int insert(T entity);
// 根据ID删除
int deleteById(Serializable id);
// 根据ID更新
int updateById(@Param(Constants.ENTITY) T entity);
// 根据ID查询
T selectById(Serializable id);
这些方法看似简单,但在实际使用中有几个关键细节需要注意:
- insert方法返回的是受影响的行数,而非主键ID。主键回填需要通过@TableId注解配置
- updateById方法使用@Param(Constants.ENTITY)注解,这是MyBatis-Plus的固定写法
- selectById的参数类型是Serializable,意味着不仅可以用Long/String,也可以用UUID等类型
2.2 条件操作方法
除了基础CRUD,BaseMapper还提供了一系列条件操作方法:
java复制// 根据条件查询列表
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据条件删除
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据条件更新
int update(@Param(Constants.ENTITY) T entity,
@Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
这些方法的核心在于Wrapper参数的使用。以查询为例,我们可以这样构建复杂条件:
java复制QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "张")
.between("age", 20, 30)
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);
我在实际项目中发现,很多开发者会犯一个典型错误:在循环中重复创建Wrapper实例。正确的做法应该是复用Wrapper对象,或者使用Lambda方式:
java复制// 推荐写法
List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery()
.like(User::getName, "张")
.between(User::getAge, 20, 30));
3. IService 扩展方法解析
3.1 批量操作方法
IService接口在BaseMapper基础上扩展了批量操作方法,这是实际项目中最常用的功能之一:
java复制// 批量插入
boolean saveBatch(Collection<T> entityList);
// 批量插入(指定批次大小)
boolean saveBatch(Collection<T> entityList, int batchSize);
// 批量更新
boolean updateBatchById(Collection<T> entityList);
这些批量方法在实现上有几个关键点需要注意:
- 默认情况下,saveBatch采用的是逐条插入的方式,要启用真正的批量插入需要配置jdbcUrl参数:
code复制jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true - batchSize参数建议设置在500-1000之间,过大会导致内存压力,过小则影响性能
- 批量更新操作需要确保实体类中包含@Version注解的乐观锁字段,否则可能产生覆盖更新
3.2 链式查询方法
IService提供了更符合业务场景的链式查询方法:
java复制// 查询一条记录
T getOne(Wrapper<T> queryWrapper);
// 查询数量
long count(Wrapper<T> queryWrapper);
// 判断是否存在
boolean exists(Wrapper<T> queryWrapper);
其中getOne()方法有个容易踩坑的地方:当查询结果有多条记录时,默认会抛出异常。如果需要取第一条而不报错,可以这样使用:
java复制User user = userService.getOne(Wrappers.<User>lambdaQuery()
.eq(User::getDeptId, 1), false); // 第二个参数表示是否抛出异常
4. 分页查询实现方案
4.1 基础分页配置
MyBatis-Plus的分页功能需要先配置分页拦截器:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
配置完成后,就可以使用Page对象进行分页查询:
java复制// 构建分页参数
Page<User> page = new Page<>(1, 10); // 当前页,每页大小
// 执行分页查询
Page<User> result = userService.page(page, Wrappers.<User>lambdaQuery()
.eq(User::getStatus, 1));
// 获取分页数据
List<User> records = result.getRecords();
long total = result.getTotal();
4.2 自定义分页查询
对于复杂的分页需求,可以在Mapper中定义自己的分页方法:
java复制// Mapper接口
IPage<User> selectUserPage(IPage<User> page, @Param("params") Map<String, Object> params);
// XML映射
<select id="selectUserPage" resultType="User">
SELECT * FROM user
WHERE status = #{params.status}
<if test="params.name != null and params.name != ''">
AND name LIKE CONCAT('%', #{params.name}, '%')
</if>
</select>
在实际项目中,我发现很多开发者会忽略分页查询的性能问题。特别是当使用left join等复杂查询时,建议:
- 避免在分页查询中使用select *
- 对大表分页时,使用基于索引的条件过滤
- 考虑使用延迟关联优化深分页性能
5. 条件构造器高级用法
5.1 动态SQL构建
MyBatis-Plus的条件构造器支持强大的动态SQL构建能力:
java复制// 动态条件查询
String name = "张";
Integer minAge = 20;
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(name), "name", name)
.ge(minAge != null, "age", minAge)
.orderByAsc("id");
这种写法等价于XML中的:
xml复制<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
</where>
ORDER BY id ASC
5.2 嵌套条件与OR逻辑
对于更复杂的查询条件,可以使用嵌套条件:
java复制wrapper.nested(qw -> qw.like("name", "张").or().like("name", "李"))
.and(qw -> qw.gt("age", 18).lt("age", 30));
对应的SQL为:
sql复制WHERE (name LIKE '%张%' OR name LIKE '%李%')
AND (age > 18 AND age < 30)
在项目实践中,我发现Lambda方式的嵌套条件可读性更好:
java复制wrapper.nested(qw -> qw.lambda()
.like(User::getName, "张")
.or()
.like(User::getName, "李"))
.and(qw -> qw.lambda()
.gt(User::getAge, 18)
.lt(User::getAge, 30));
6. 扩展方法与自定义实现
6.1 自定义BaseMapper方法
虽然MyBatis-Plus提供了丰富的方法,但实际项目中我们经常需要扩展:
java复制public interface UserMapper extends BaseMapper<User> {
// 批量插入(返回主键)
int insertBatchSomeColumn(List<User> userList);
// 逻辑删除(带审计字段更新)
int logicDeleteById(@Param("id") Long id, @Param("operator") String operator);
}
对应的XML实现:
xml复制<insert id="insertBatchSomeColumn" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.age})
</foreach>
</insert>
<update id="logicDeleteById">
UPDATE user SET
is_deleted = 1,
update_time = NOW(),
update_by = #{operator}
WHERE id = #{id} AND is_deleted = 0
</update>
6.2 Service层扩展
在Service层,我们可以通过继承ServiceImpl来实现自定义逻辑:
java复制@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
@Override
public boolean customUpdate(User user) {
// 前置处理
if (user.getAge() < 18) {
throw new RuntimeException("未成年人禁止操作");
}
// 调用父类方法
return updateById(user);
}
}
这种扩展方式既保留了MyBatis-Plus的内置方法,又能加入业务特定的逻辑校验。
7. 性能优化与最佳实践
7.1 批量操作优化
对于大批量数据操作,建议采用以下优化方案:
- 使用rewriteBatchedStatements参数开启真正的批量插入
- 合理设置batchSize(通常500-1000为宜)
- 考虑使用并行流处理(注意线程安全):
java复制// 并行批量插入
List<List<User>> partitions = Lists.partition(userList, 500);
partitions.parallelStream().forEach(batch -> {
userService.saveBatch(batch, batch.size());
});
7.2 查询优化建议
- 避免在循环中查询数据库,改用批量查询+内存处理
- 使用selectMaps()获取部分字段,减少数据传输量
- 对高频查询考虑添加二级缓存
java复制// 只查询需要的字段
List<Map<String, Object>> users = userMapper.selectMaps(
Wrappers.<User>query()
.select("id", "name")
.eq("status", 1)
);
7.3 事务管理
MyBatis-Plus本身不提供事务管理,需要结合Spring事务使用:
java复制@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
public void batchUpdateUsers(List<User> users) {
for (User user : users) {
if (user.getAge() > 100) {
throw new RuntimeException("年龄异常");
}
userMapper.updateById(user);
}
}
}
在实际项目中,我发现很多开发者会忽略事务的传播行为。比如在批量操作中,如果某个子操作失败,应该确保整个批量操作回滚。
