1. BaseMapper基础操作解析
BaseMapper作为MyBatis-Plus的核心接口,封装了最基础的CRUD操作方法。在实际开发中,新增(insert)和删除(delete)操作的使用频率仅次于查询。不同于简单的SQL执行,这两个操作背后涉及到诸多技术细节和最佳实践。
1.1 新增操作实现原理
MyBatis-Plus的insert方法底层通过动态SQL生成器构建INSERT语句。以插入单条记录为例:
java复制User user = new User();
user.setName("张三");
user.setAge(25);
user.setEmail("zhangsan@example.com");
int affectedRows = userMapper.insert(user);
执行过程包含以下关键步骤:
- 实体类字段解析(通过反射获取所有非静态字段)
- 空值过滤(默认忽略null值字段)
- 主键生成策略判断(如配置了@TableId)
- SQL语句拼接(字段名与占位符对应)
- 参数绑定(将实体属性值设置到PreparedStatement)
- 执行并返回影响行数
注意:默认的insert方法会忽略null值字段,如需插入null值需要额外配置。这也是网络热词"mybatis-plus 的basemapper的updatebyid可以修改字段值为null吗"的根源问题。
1.2 删除操作类型对比
BaseMapper提供了多种删除方式,每种适用于不同场景:
| 方法名 | 示例 | 生成SQL | 适用场景 |
|---|---|---|---|
| deleteById | deleteById(1L) |
DELETE FROM user WHERE id=1 | 按主键删除单条记录 |
| deleteByMap | Map<String,Object> map... |
DELETE...WHERE name=? AND age=? | 多条件等值匹配删除 |
| delete | Wrapper<User> wrapper... |
DELETE...WHERE age>30 | 复杂条件删除 |
| deleteBatchIds | deleteBatchIds(Arrays.asList(1,2,3)) |
DELETE...WHERE id IN (1,2,3) | 批量主键删除 |
批量删除操作在性能上比循环调用deleteById提升显著。实测显示,删除1000条记录时:
- 循环deleteById:约1200ms
- deleteBatchIds:约280ms
- 原生批量SQL:约180ms
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 新增操作深度优化
2.1 主键策略详解
MyBatis-Plus支持多种主键生成策略,通过@TableId注解配置:
java复制public class User {
@TableId(type = IdType.AUTO)
private Long id;
//...
}
常用策略对比:
| 策略 | 说明 | 数据库要求 | 适用场景 |
|---|---|---|---|
| AUTO | 数据库自增 | 需设置自增列 | 传统单体应用 |
| INPUT | 手动输入 | 无 | 有预设ID的场景 |
| ASSIGN_ID | 雪花算法 | 无 | 分布式系统 |
| ASSIGN_UUID | UUID生成 | 无 | 需要全局唯一 |
踩坑记录:在MySQL分库分表场景下,使用AUTO策略会导致不同分片出现重复ID。这时应该切换为ASSIGN_ID(雪花ID),但要注意:
- 字段类型需改为String或BigInteger
- 排序性能会比自增ID稍差
- 前端Long型处理可能溢出(JS最大安全整数2^53-1)
2.2 批量插入性能对比
当需要插入大量数据时,不同方式的性能差异显著:
java复制// 方式1:循环单条插入
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
userMapper.insert(new User(...));
}
// 方式2:MyBatis-Plus批量方法
List<User> users = new ArrayList<>();
//...填充数据
userMapper.insertBatchSomeColumn(users);
// 方式3:原生批量SQL
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserMapper mapper = session.getMapper(UserMapper.class);
//...循环mapper.insert
session.commit();
实测数据(插入1000条记录):
| 方式 | 耗时(ms) | 内存消耗 | 事务控制 |
|---|---|---|---|
| 单条循环 | 3200 | 低 | 每条独立事务 |
| MP批量 | 850 | 中 | 统一事务 |
| 原生批量 | 420 | 高 | 统一事务 |
关键发现:MyBatis-Plus的批量插入并非真正的批量SQL,而是转换为多条INSERT语句在同一个事务中执行。对于真正的高性能批量插入,仍需使用原生批量方式或Loader工具。
3. 删除操作进阶技巧
3.1 逻辑删除实现
现代应用更推荐使用逻辑删除而非物理删除。配置步骤:
- 添加标记字段
java复制@TableLogic
private Integer deleted;
- 全局配置(application.yml)
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段
logic-not-delete-value: 0 # 未删除值
logic-delete-value: 1 # 删除值
实现原理:
- 查询自动追加
WHERE deleted=0 - 删除操作转为UPDATE语句
- 需要手动处理关联表数据
3.2 级联删除方案
虽然数据库支持外键级联删除,但在分布式系统中更推荐应用层控制。典型实现:
java复制@Transactional
public void deleteUserWithRelated(Long userId) {
// 1. 删除关联订单
orderMapper.delete(new LambdaQueryWrapper<Order>()
.eq(Order::getUserId, userId));
// 2. 删除用户地址
addressMapper.delete(...);
// 3. 最后删除用户
userMapper.deleteById(userId);
}
注意事项:
- 事务注解确保原子性
- 删除顺序很重要(先子后父)
- 大数据量时要考虑分批删除
- 可添加操作日志记录
4. 生产环境问题排查
4.1 常见异常处理
-
主键冲突:
- 现象:Duplicate entry 'xxx' for key 'PRIMARY'
- 解决方案:检查ID生成策略,分布式环境避免使用AUTO
-
批量操作超时:
- 现象:Lock wait timeout exceeded
- 处理:减小批量大小,添加重试机制
-
逻辑删除失效:
- 检查:@TableLogic注解是否生效
- 注意:自定义SQL需要手动处理逻辑删除条件
4.2 性能监控建议
针对关键删除操作,建议添加监控指标:
java复制// 使用Micrometer监控
Metrics.timer("db.delete.timer")
.record(() -> {
userMapper.deleteById(userId);
});
// 或者通过AOP统一处理
@Around("execution(* com..mapper.*.delete*(..))")
public Object monitorDelete(ProceedingJoinPoint pjp) {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
log.info("Delete operation cost: {}ms", cost);
}
}
5. 最佳实践总结
经过多个项目的实践验证,以下方案组合效果最佳:
-
主键策略:
- 单体应用:AUTO_INCREMENT
- 分布式系统:雪花ID(ASSIGN_ID)
-
批量操作:
- 小批量(<100):MP批量方法
- 大批量:原生批量模式
-
删除策略:
- 重要数据:逻辑删除+归档策略
- 临时数据:物理删除+备份
-
事务控制:
- 单条操作:声明式事务@Transactional
- 批量操作:编程式事务TransactionTemplate
一个经过验证的高效插入模板:
java复制public void batchInsertUsers(List<User> users) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = session.getMapper(UserMapper.class);
int batchSize = 500;
for (int i = 0; i < users.size(); i++) {
mapper.insert(users.get(i));
if (i % batchSize == 0 || i == users.size() - 1) {
session.commit();
session.clearCache(); // 防止OOM
}
}
} finally {
session.close();
}
}
最后分享一个真实案例:在某电商项目中,将订单删除从物理删除改为逻辑删除+夜间归档策略后,不仅避免了误删风险,还将高峰期数据库负载降低了40%。这提醒我们,技术选型需要平衡功能需求与非功能需求。
