在MyBatis-Plus开发中,实体类字段类型的选择往往被忽视,但这个小细节却可能引发大问题。本文将深度解析为什么包装类优于基本类型,并通过真实案例展示潜在风险。
一、问题背景:一个血泪教训
先看这个实体类定义,你是否也这样写过?
public class User {
private int id; // 基本类型
private String name;
private int age; // 基本类型
private int status; // 基本类型
// getters/setters
}
当执行以下更新操作时:
User user = new User();
user.setId(1);
user.setName("张三");
userMapper.updateById(user);
灾难发生了:所有未显式赋值的字段(age, status)被更新为0!这是因为基本类型默认值导致的非预期行为。
二、核心区别:基本类型 vs 包装类
特性 | 基本类型(如int) | 包装类(如Integer) |
---|---|---|
默认值 | 0 | null |
数据库NULL映射 | 无法表示 | 可以表示 |
空值检测 | 困难 | 简单 |
自动更新策略 | 风险高 | 安全 |
内存占用 | 较低 | 稍高 |
三、为什么MyBatis-Plus强烈推荐包装类?
1. NULL语义的正确表达
当数据库字段值为NULL时:
// 基本类型
int age = user.getAge(); // 返回0,但实际数据库是NULL!
// 包装类
Integer age = user.getAge(); // 返回null,正确反映数据库状态
2. 避免全字段更新灾难
MyBatis-Plus的更新策略:
// 当字段为基本类型且未赋值时
UpdateWrapper<User> update = new UpdateWrapper<>();
update.set("name", "张三");
// 其他基本类型字段会被更新为0!
// 使用包装类时
User user = new User();
user.setId(1);
user.setName("张三");
// 其他字段为null,MyBatis-Plus将忽略这些字段
3. 条件查询的准确性
QueryWrapper<User> query = new QueryWrapper<>();
query.eq("status", 0);
// 基本类型:会匹配status=0和status IS NULL的记录!
// 包装类:精确匹配status=0
4. 逻辑删除的安全实现
当使用@TableLogic
时:
@TableLogic
private Integer deleted; // 必须包装类
// 基本类型int会导致:
// - 新插入记录默认0(未删除),但无法表示NULL
// - 无法区分"未删除"和"值为0"
四、真实场景问题分析
场景1:部分字段更新
// 错误做法(基本类型)
User u = new User();
u.setId(1);
u.setName("更新名称");
updateById(u); // 其他字段被设为0!
// 正确做法(包装类)
User u = new User();
u.setId(1);
u.setName("更新名称");
updateById(u); // 只更新name字段
场景2:动态查询条件
// 危险查询(基本类型)
public List<User> queryUsers(int status) {
QueryWrapper<User> qw = new QueryWrapper<>();
if(status >= 0) { // 试图过滤状态
qw.eq("status", status);
}
return mapper.selectList(qw);
}
// 当status=0时,会返回status=0和status IS NULL的记录!
五、最佳实践建议
1. 实体类正确定义
public class SafeEntity {
// 主键推荐使用包装类
@TableId(type = IdType.AUTO)
private Long id;
// 普通字段使用包装类
private Integer age;
private BigDecimal account;
// 逻辑删除字段必须包装类
@TableLogic
private Integer deleted;
}
2. 更新操作规范
// 推荐:使用UpdateWrapper明确更新字段
UpdateWrapper<User> update = new UpdateWrapper<>();
update.set("name", "李四")
.eq("id", 1);
userMapper.update(null, update);
// 或使用lambda表达式
userMapper.update(Wrappers.<User>lambdaUpdate()
.set(User::getName, "李四")
.eq(User::getId, 1));
3. 查询条件处理
public List<User> safeQuery(Integer status) {
return userMapper.selectList(new QueryWrapper<User>()
.eq(status != null, "status", status));
}
4. 全局配置(可选)
mybatis-plus:
global-config:
db-config:
select-strategy: not_empty # 只更新非空字段
insert-strategy: not_empty
六、性能考量:包装类真的更差吗?
虽然包装类比基本类型占用更多内存,但在现代JVM优化下:
- 对象重用:Integer缓存-128~127的值
- 栈上分配:JIT编译器优化
- 实际影响:在典型Web应用中,实体对象内存占比通常小于1%
- 权衡结果:正确性 >> 微小的性能差异
七、总结:包装类的核心优势
场景 | 基本类型风险 | 包装类优势 |
---|---|---|
数据库NULL映射 | 丢失信息(0代替NULL) | 正确表达NULL语义 |
部分字段更新 | 覆盖未赋值字段为0 | 只更新非空字段 |
条件查询 | 混淆0和NULL | 精确区分0和NULL |
逻辑删除 | 无法正确实现 | 完美支持 |
参数传递 | 无法表示"未设置" | null表示未设置 |
📌 黄金准则:在MyBatis-Plus实体类中,总是优先使用包装类。这不仅是规范问题,更是避免生产事故的关键防线!
踩坑感悟:笔者曾因一个int字段导致全表错误更新,花3小时回滚数据。看似微小的类型选择,可能引发雪崩效应。坚持使用包装类,让MyBatis-Plus更安全可控!