jinji
发布于 2025-08-21 / 40 阅读
0
0

MyBatis-Flex QueryWrapper 自动添加意外查询条件问题分析与解决

🐛 问题现象

在使用 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());

根本原因分解

  1. Lombok 注解影响

    @Data(staticConstructor = "create")  // 生成 create() 静态方法
    public class SysRole extends TenantBaseEntity<SysRole> {
        private int roleSort;  // 基本类型,默认值为 0
    }
    
  2. MyBatis-Flex 行为机制

    • SysRole.create() 创建实例时,所有字段被初始化为默认值
    • roleSort 作为 int 类型被初始化为 0
    • QueryWrapper.create(entity) 会将实体中所有非空字段自动作为查询条件
  3. 继承链影响

    • SysRole 继承 TenantBaseEntity
    • TenantBaseEntity 中的 @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") 生成的实例会初始化所有字段为默认值,从而导致意外的查询条件被添加。

关键教训

  1. 在构建查询条件时,要明确控制条件的添加,避免依赖框架的自动映射机制
  2. 合理使用基本类型和包装类型,利用包装类型的 null 特性来避免意外条件
  3. 优先使用 QueryWrapper.create().from("table") 而不是传入实体实例的方式


评论