商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 如何正确映射数据库结果集为包含完整管理层级关系的 Employee 集合

如何正确映射数据库结果集为包含完整管理层级关系的 Employee 集合

  发布于2026-07-11 阅读(0)

扫一扫,手机访问

如何正确地将扁平化的JDBC结果集,映射成一个包含完整上下级关系的 Employee 集合?这看起来是个小问题,但实操中却藏着不少坑。本文将介绍一套可靠的两阶段映射法,彻底告别数据顺序带来的不确定性。

在实际的业务开发中,处理员工-经理这种自关联关系的数据,是一个很常见的场景。但不少人在用JDBC映射时,会习惯性地在遍历ResultSet的过程中,直接就把Manager的引用给填上。这么做,说白了,就是赌数据库返回数据的顺序一定是经理记录在前、下属记录在后。

问题是,现实世界里的SQL查询,如果不加ORDER BY,其返回顺序从来都不是一个可靠的约定。不同的数据库、不同的执行计划,甚至同一条SQL在不同时间跑,结果顺序都可能不一样。一旦经理记录晚于下属出现,那就只能得到一个manager == null的静默错误,排查起来相当头疼。

✅ 正确方案:两阶段映射法

解决思路其实很简单:把“创建对象”和“建立关系”这两个步骤拆开来做,各司其职,互不干扰。

  1. 第一阶段(遍历 + 构建):单遍扫描ResultSet,只干两件事。第一,根据每行数据创建Employee实例,此时其manager字段先留空。第二,把这些实例按ID缓存到一个Map里,同时再用另一个Map记录下“谁的经理是谁”这个映射关系。
  2. 第二阶段(关系注入):遍历那张关系映射表,从缓存中把对应的员工和经理都找出来,然后通过setter方法,或者用新的构造器,完成引用注入。

✅ 场景一:Employee 类支持 setter(简单场景的首选)

如果Employee类允许修改manager属性,那事情就简单多了。具体代码如下,逻辑很清晰:

public SetMapper> employeesSetMapper() {    return resultSet -> {        Set employees = new HashSet<>();        Map employeeMap = new HashMap<>();        Map employeeToManagerMap = new HashMap<>();        while (resultSet.next()) {            BigInteger id = BigInteger.valueOf(resultSet.getInt(1));            FullName fullName = new FullName(                resultSet.getString("firstName"),                resultSet.getString("lastName"),                resultSet.getString("middleName")            );            Position position = Position.valueOf(resultSet.getString("position"));            LocalDate hired = resultSet.getDate(7).toLocalDate();            BigDecimal salary = resultSet.getBigDecimal("salary");            BigInteger managerId = BigInteger.valueOf(resultSet.getInt(6));            // 注意:这里需要根据业务逻辑过滤掉无效的managerId,比如0或NULL            if (managerId != null && !managerId.equals(BigInteger.ZERO)) {                employeeToManagerMap.put(id, managerId);            }            Employee employee = new Employee(id, fullName, position, hired, salary, null);            employees.add(employee);            employeeMap.put(id, employee);        }        // 第二阶段:批量注入 manager 引用        employeeToManagerMap.forEach((employeeId, managerId) -> {            Employee employee = employeeMap.get(employeeId);            Employee manager = employeeMap.get(managerId);            if (employee != null && manager != null) {                employee.setManager(manager);            }        });        return employees;    };}

⚠️ 几点说明:

  • 这里假设Employee.setManager()是公开的;
  • 对于managerId的过滤校验,一定要结合实际业务规则,防止把占位符当成真实ID;
  • 这套方案完全不依赖ORDER BY,结果集无论什么顺序都能正确处理。

✅ 场景二:Employee 类不可变(无 setter,仅构造器)

如果Employee是一个不可变对象,比如用了Ja va 14的record,或者所有字段都是final的,那又该怎么办?这时候就需要用递归重建的策略了,确保每一层级的manager都能被正确地注入,包括嵌套的管理链:

public SetMapper> employeesSetMapper() {    return resultSet -> {        Map employeeMap = new HashMap<>();        Map employeeToManagerMap = new HashMap<>();        // 阶段一:仅构建基础 Employee(manager = null)        while (resultSet.next()) {            BigInteger id = BigInteger.valueOf(resultSet.getInt(1));            FullName fullName = new FullName(                resultSet.getString("firstName"),                resultSet.getString("lastName"),                resultSet.getString("middleName")            );            Position position = Position.valueOf(resultSet.getString("position"));            LocalDate hired = resultSet.getDate(7).toLocalDate();            BigDecimal salary = resultSet.getBigDecimal("salary");            BigInteger managerId = BigInteger.valueOf(resultSet.getInt(6));            if (managerId != null && !managerId.equals(BigInteger.ZERO)) {                employeeToManagerMap.put(id, managerId);            }            Employee employee = new Employee(id, fullName, position, hired, salary, null);            employeeMap.put(id, employee);        }        // 阶段二:递归构建含完整 manager 链的 Employee        Map enrichedMap = new HashMap<>();        for (BigInteger id : employeeMap.keySet()) {            enrichedMap.put(id, getEmployeeWithManager(id, employeeMap, employeeToManagerMap));        }        return new HashSet<>(enrichedMap.values());    };}private static Employee getEmployeeWithManager(    BigInteger employeeId,    Map employeeMap,    Map employeeToManagerMap) {    Employee base = employeeMap.get(employeeId);    BigInteger managerId = employeeToManagerMap.get(employeeId);    if (managerId == null || !employeeMap.containsKey(managerId)) {        return base; // 无经理或经理不存在    }    Employee manager = getEmployeeWithManager(managerId, employeeMap, employeeToManagerMap);    return new Employee(        base.getId(),        base.getFullName(),        base.getPosition(),        base.getHireDate(),        base.getSalary(),        manager    );}

? 这个方案的好处很明显:

  • 它能支持任意深度的管理链,从CEO到普通员工,一路串下来没问题;
  • 由于employeeMap在递归前已完整构建,这天然就能避免循环引用;
  • 完全符合函数式编程的理念,保持了对象的不变性。

✅ 总结

下面这张表格,可以帮你快速了解不同方案的特点:

方案 适用条件 是否依赖顺序 可扩展性
单次遍历(原始) 经理记录严格前置 ✅ 强依赖 ❌ 易崩坏
两阶段 + setter Employee 可变 ❌ 无关 ✅ 简洁高效
递归重建 Employee 不可变 ❌ 无关 ✅ 支持深层嵌套

说白了,不管选择哪种方式,核心原则都是一致的:彻底放弃对SQL结果集顺序的任何幻象。编写健壮的数据访问代码,确定性远比巧合更重要。

本文转载于:https://www.php.cn/faq/2386334.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注