🐛 问题现象
在使用 MyBatis-Flex 构建查询条件时,发现 SQL 中自动添加了不应该存在的条件:
SELECT COUNT(*) AS `total` FROM `sys_role` WHERE `tenant_id` = ? AND (`role_sort` = ?) AND `del_flag` = ?
-- 参数: 000000(String), 0(Integer), 0(Integer)
其中 role_sort = 0 这个条件在代码中并未显式添加。
🔍 问题分析
核心原因
问题出现在以下代码:
QueryWrapper wrapper = QueryWrapper.create(SysRole.create());
根本原因分解
Lombok 注解影响
@Data(staticConstructor = "create") // 生成 create() 静态方法 public class SysRole extends TenantBaseEntity<SysRole> { private int roleSort; // 基本类型,默认值为 0 }MyBatis-Flex 行为机制
SysRole.create()创建实例时,所有字段被初始化为默认值roleSort作为int类型被初始化为0QueryWrapper.create(entity)会将实体中所有非空字段自动作为查询条件
继承链影响
SysRole继承TenantBaseEntityTenantBaseEntity中的@Column(tenantId = true)也会被自动处理- 逻辑删除字段
@Column(isLogicDelete = true)也会被自动处理
🎯 为什么改成包装类型可以解决问题?
基本类型 vs 包装类型的差异
// 基本类型 - 有默认值
private int roleSort; // 默认值: 0
private boolean status; // 默认值: false
// 包装类型 - 默认为 null
private Integer roleSort; // 默认值: null
private Boolean status; // 默认值: null
MyBatis-Flex 的判断逻辑
MyBatis-Flex 在处理 QueryWrapper.create(entity) 时,只会将非空字段作为查询条件:
// 基本类型的情况
SysRole role = SysRole.create();
// role.getRoleSort() 返回 0 (不是 null!)
// MyBatis-Flex 认为这是有效值,添加条件: role_sort = 0
// 包装类型的情况
SysRole role = SysRole.create();
// role.getRoleSort() 返回 null
// MyBatis-Flex 认为这是空值,不添加任何条件
源码层面的判断逻辑(简化示意)
// MyBatis-Flex 内部类似这样的判断逻辑
if (fieldValue != null) { // 关键判断
// 添加查询条件
addCondition(fieldName, fieldValue);
}
✅ 解决方案
✅ 推荐方案:修改 QueryWrapper 创建方式
// 错误写法
QueryWrapper wrapper = QueryWrapper.create(SysRole.create());
// 正确写法
QueryWrapper wrapper = QueryWrapper.create().from("sys_role");
🎯 完整修复代码
private QueryWrapper buildQueryWrapper(SysRoleBO sysRoleBO) {
// 使用表名直接创建,避免实体实例自动添加条件
QueryWrapper wrapper = QueryWrapper.create().from("sys_role");
if (ObjectUtil.isNotNull(sysRoleBO.getRoleId())){
wrapper.eq("role_id", sysRoleBO.getRoleId());
}
if (StringUtils.isNotBlank(sysRoleBO.getRoleName())) {
wrapper.like("role_name", sysRoleBO.getRoleName());
}
if (StringUtils.isNotBlank(sysRoleBO.getStatus())) {
wrapper.eq("status", sysRoleBO.getStatus());
}
if (StringUtils.isNotBlank(sysRoleBO.getRoleKey())) {
wrapper.like("role_key", sysRoleBO.getRoleKey());
}
log.debug("wrapper sql is :{}", wrapper.toSQL());
return wrapper;
}
🔧 备选方案
方案一:修改字段类型(利用 null 值特性)
// 将基本类型改为包装类型,利用 null 值避免自动添加条件
private Integer roleSort; // 而不是 int roleSort;
private Boolean status; // 而不是 boolean status;
方案二:调整 Lombok 注解
// 移除 staticConstructor
@Data
public class SysRole extends TenantBaseEntity<SysRole> {
// ...
}
📚 最佳实践建议
❌ 避免的做法
// 不要传入实体实例来创建 QueryWrapper
QueryWrapper wrapper = QueryWrapper.create(entityInstance);
✅ 推荐的做法
// 明确指定表名创建 QueryWrapper
QueryWrapper wrapper = QueryWrapper.create().from("table_name");
// 或者使用 @Table 注解的实体类(但要小心字段默认值)
QueryWrapper wrapper = QueryWrapper.create().from(Entity.class);
💡 字段设计建议
- 查询实体字段建议使用包装类型(Integer、Long、Boolean等)
- 避免基本类型字段在查询条件中产生意外的默认值匹配
- 明确控制哪些字段参与查询条件构建
- 利用包装类型的
null特性来区分"未设置"和"设置为默认值"
🎯 总结
这个问题的核心是 MyBatis-Flex 的 QueryWrapper.create(entity) 会自动将实体中的非空字段作为查询条件,而 Lombok 的 @Data(staticConstructor = "create") 生成的实例会初始化所有字段为默认值,从而导致意外的查询条件被添加。
关键教训:
- 在构建查询条件时,要明确控制条件的添加,避免依赖框架的自动映射机制
- 合理使用基本类型和包装类型,利用包装类型的
null特性来避免意外条件 - 优先使用
QueryWrapper.create().from("table")而不是传入实体实例的方式