1. BaseMapper 新增与删除操作深度解析
作为 MyBatis-Plus 框架中最核心的接口之一,BaseMapper 提供的 CRUD 功能是日常开发中使用频率最高的操作。今天我们就来深入探讨其中新增(insert)和删除(delete)这两个基础但至关重要的方法实现原理与实战技巧。
1.1 BaseMapper 接口概述
BaseMapper 是 MyBatis-Plus 提供的一个通用 Mapper 接口,它封装了常见的数据库操作,开发者只需继承这个接口,无需编写任何方法实现,就能获得完整的 CRUD 功能。这种设计极大地简化了 DAO 层的开发工作。
BaseMapper 中与新增和删除相关的主要方法包括:
java复制int insert(T entity);
int deleteById(Serializable id);
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
int delete(@Param("ew") Wrapper<T> wrapper);
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
1.2 新增操作实现原理
insert 方法是 BaseMapper 中最基础的新增方法,其底层实现主要经过以下几个步骤:
-
SQL 解析阶段:MyBatis-Plus 会解析实体类的注解信息,包括 @TableName、@TableField 等,确定表名和字段映射关系。
-
主键生成策略处理:根据 @TableId 注解配置的主键策略(如 AUTO、INPUT、ASSIGN_ID 等),生成或获取主键值。
-
字段过滤处理:根据 @TableField 注解的 insert 属性值(默认为 true)决定是否包含该字段到 INSERT 语句中。
-
SQL 构建:最终生成的 SQL 语句格式为:
sql复制INSERT INTO table_name (column1, column2,...) VALUES (value1, value2,...)
重要提示:默认情况下,insert 方法会忽略实体类中值为 null 的字段,这与 updateById 方法的处理方式不同(后者默认会更新字段为 null 的值)。
1.3 删除操作实现原理
BaseMapper 提供了多种删除方式,每种方式的实现机制有所不同:
1.3.1 按主键删除(deleteById)
这是最简单的删除方式,其 SQL 构建逻辑为:
sql复制DELETE FROM table_name WHERE id = ?
底层会先检查主键是否存在,如果不存在则抛出异常(取决于全局配置的 logic-delete 设置)。
1.3.2 按条件构造器删除(delete)
这是最灵活的删除方式,支持复杂的条件构造:
java复制QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三").gt("age", 18);
userMapper.delete(wrapper);
生成的 SQL 类似于:
sql复制DELETE FROM user WHERE name = '张三' AND age > 18
1.3.3 批量删除(deleteBatchIds)
对于批量删除操作,MyBatis-Plus 会生成如下 SQL:
sql复制DELETE FROM user WHERE id IN (1, 2, 3)
性能提示:批量删除的 IN 条件数量不宜过多(建议不超过 1000),否则可能导致数据库性能问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 新增操作实战技巧
2.1 主键策略选择与配置
MyBatis-Plus 支持多种主键生成策略,通过 @TableId 注解配置:
java复制public class User {
@TableId(type = IdType.AUTO)
private Long id;
// 其他字段...
}
常用策略说明:
- AUTO:数据库自增(需要数据库支持)
- INPUT:手动输入
- ASSIGN_ID:雪花算法生成 ID(默认)
- ASSIGN_UUID:生成 UUID
2.2 字段插入控制
通过 @TableField 注解可以精细控制字段的插入行为:
java复制public class User {
@TableField(insert = false)
private String remark; // 该字段不会出现在 INSERT 语句中
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime; // 只在插入时自动填充
}
2.3 批量插入性能优化
虽然 BaseMapper 没有直接提供批量插入方法,但可以通过以下方式实现高效批量插入:
java复制// 方式1:使用 SQL 注入器扩展
@Autowired
private SqlInjector sqlInjector;
// 方式2:使用 Service 层的 saveBatch 方法
userService.saveBatch(userList);
// 方式3:手动批量处理
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : userList) {
mapper.insert(user);
}
session.commit();
} finally {
session.close();
}
性能对比:在万级数据量测试中,批量方式比循环单条插入快 10-50 倍。
3. 删除操作实战技巧
3.1 逻辑删除配置
在实际项目中,我们通常采用逻辑删除而非物理删除。配置方法:
- 全局配置:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-not-delete-value: 0 # 未删除值
logic-delete-value: 1 # 删除值
- 实体类字段添加 @TableLogic 注解:
java复制@TableLogic
private Integer deleted;
配置后,所有的删除操作将自动变为 UPDATE 语句:
sql复制UPDATE user SET deleted=1 WHERE id=?
3.2 条件构造器高级用法
删除操作中最强大的功能是条件构造器,支持各种复杂条件:
java复制// 1. 链式调用
wrapper.eq("name", "张三")
.or()
.gt("age", 18)
.lt("age", 30);
// 2. Lambda 表达式写法
wrapper.lambda()
.eq(User::getName, "张三")
.between(User::getAge, 18, 30);
// 3. 嵌套条件
wrapper.nested(w -> w.eq("type", 1).or().eq("status", 2))
.apply("date_format(create_time,'%Y-%m-%d')={0}", "2023-01-01");
3.3 性能优化建议
- 大批量删除建议分批次进行,避免长事务和锁表:
java复制List<Long> idList = /* 获取ID列表 */;
List<List<Long>> partitions = Lists.partition(idList, 1000);
partitions.forEach(partition -> userMapper.deleteBatchIds(partition));
- 对于无索引条件的删除操作,建议先查询再删除:
java复制List<User> users = userMapper.selectList(wrapper);
if (!users.isEmpty()) {
userMapper.delete(wrapper);
}
4. 常见问题与解决方案
4.1 新增操作常见问题
问题1:如何获取插入后的自增主键?
解决方案:
java复制User user = new User();
user.setName("张三");
userMapper.insert(user);
// 插入后自动回填主键
Long id = user.getId();
问题2:字段值为 null 时不被插入怎么办?
解决方案:
- 全局配置:
yaml复制mybatis-plus:
global-config:
db-config:
insert-strategy: not_empty
- 字段级别配置:
java复制@TableField(insertStrategy = FieldStrategy.IGNORED)
private String remark;
4.2 删除操作常见问题
问题1:删除不存在的记录会报错吗?
默认情况下,物理删除不存在的记录不会报错(影响行数为0)。但如果是逻辑删除且配置了相关拦截器,可能会抛出异常。
问题2:如何实现多租户条件下的安全删除?
解决方案:
- 添加多租户拦截器
- 自动注入租户条件:
java复制wrapper.eq("tenant_id", TenantContext.getCurrentTenantId());
4.3 性能问题排查
问题:删除操作执行缓慢
排查步骤:
- 检查是否使用了索引字段作为条件
- 检查是否开启了逻辑删除(逻辑删除会转为 UPDATE)
- 检查是否有触发器或级联操作
- 检查数据库锁情况
优化方案:
sql复制-- 添加合适索引
CREATE INDEX idx_name ON user(name);
-- 分批删除
DELETE FROM user WHERE id BETWEEN 1 AND 1000;
5. 高级应用场景
5.1 审计字段自动填充
结合 MetaObjectHandler 实现自动填充创建时间、更新时间等字段:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
5.2 多数据源下的操作
在多数据源环境下,需要特别注意:
- 确保操作的数据源一致
- 事务注解要指定正确的事务管理器
- 批量操作要在同一事务中完成
5.3 自定义 SQL 注入器
扩展 BaseMapper 功能,添加自定义方法:
java复制public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new InsertBatchSomeColumn());
return methodList;
}
}
6. 最佳实践总结
经过多个项目的实践验证,我们总结出以下最佳实践:
-
新增操作建议:
- 明确主键生成策略,分布式系统推荐使用 ASSIGN_ID
- 敏感字段不要依赖自动填充,应显式设置
- 批量插入数据量控制在 1000 条/批次以内
-
删除操作建议:
- 生产环境优先使用逻辑删除
- 删除前先查询确认数据,避免误删
- 复杂条件删除操作要添加事务注解
-
性能优化建议:
java复制// 不好的写法 for (User user : userList) { userMapper.insert(user); } // 好的写法 userService.saveBatch(userList); -
安全建议:
- 删除接口必须做权限校验
- 用户输入的删除条件要做防注入处理
- 重要数据删除前建议备份
在实际项目中,我发现很多开发人员容易忽视删除操作的事务特性。比如以下代码就存在隐患:
java复制// 有问题的写法
public void deleteUser(Long id) {
userMapper.deleteById(id);
logMapper.insert(new Log("删除用户", id));
}
应该改为:
java复制@Transactional
public void deleteUser(Long id) {
userMapper.deleteById(id);
logMapper.insert(new Log("删除用户", id));
}
