【问题标题】:JPA - calculated column as entity class property?JPA - 计算列作为实体类属性?
【发布时间】:2012-07-09 09:00:20
【问题描述】:

对 JPA 来说相对较新,所以我有一种架构问题。 假设我有具有多对一关系的表 EMPLOYEE 和 DEPARTMENT(即许多员工为一个部门工作):

EMPLOYEE
  EMPLOYEE_ID
  EMPLOYEE_NAME
  DEPARTMENT_ID 

DEPARTMENT
  DEPARTMENT_ID
  DEPARTMENT_NAME

所以我可以为 Employee 和 Department 定义适当的实体,没有问题。但是,在一个视图中,我想显示为该部门工作的员工人数的部门列表,如下所示:

SELECT D.DEPARTMENT_NAME, 
       (SELECT COUNT(*) FROM EMPLOYEE E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID) NUMBER_OF_EMPLOYEES
FROM DEPARTMENT D

我只是不确定使用 JPA 完成此任务的正确策略是什么... 我不想总是获取部门实体的员工人数,因为在需要时只有一个视图。

看起来 Hibernate 的 @Formula 是一种可能的方法,但它不符合 JPA 标准。

【问题讨论】:

    标签: database hibernate jakarta-ee jpa


    【解决方案1】:

    您可以在实体中创建一个成员作为附加列,然后在查询中使用别名引用它。 @Column 注解中的列名必须与别名匹配。

    说,对于您的原始查询,您可以添加一个countEmployees 成员,如下所示。还要添加insertable=falseupdatable=false,这样实体管理器就不会尝试将其包含在插入或更新语句中:

    public class Department {
    
        @Column(name="DEPARTMENT_ID")
        Long departmentId;
    
        @Column(name="DEPARTMENT_NAME")
        String departmentName;
    
        @Column(name="countEmployees", insertable=false, updatable=false)
        Long countEmployees;
    
        //accessors omitted
    
    }
    

    您的查询:

    SELECT D.DEPARTMENT_NAME, 
    (SELECT COUNT(*) FROM EMPLOYEE E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID) AS countEmployees
    FROM DEPARTMENT D
    

    这也适用于使用 Spring Data Jpa 存储库。

    【讨论】:

    • 您可以使用 JPA 查询而不是本机查询来做同样的事情吗?
    【解决方案2】:

    选项 1:我建议这样做是因为您不喜欢 MattR 建议的构造函数路线。您多次提到“视图”这个词,我知道您在向用户谈论视图,但为什么不在您的数据库中设置一个包含计算列的视图,然后创建一个映射到计算列的只读实体列?

    选项 2:回应您关于不想创建视图的评论。您可以创建一个包含实体和计算列的容器对象,然后就像 MattR 建议的那样,您在选择中使用新的。比如:

    public class DepartmentInfo {
        private Department department;
    
        // this might have to be long or something
        // just see what construct JPA tries to call
        private int employeeCount;
    
        public DepartmentInfo( Department d, int count ) { 
            department = d;
            employeeCount = count;
        }
        // getters and setters here
    }
    

    那么你的选择就变成了

    SELECT new my.package.DepartmentInfo( D, 
           (SELECT COUNT(*) FROM EMPLOYEE E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID))
    FROM DEPARTMENT D
    

    您可以使用 DepartmentInfo 获取您感兴趣的属性。

    【讨论】:

    • 确实有可能。但我想要的只是我现有实体的一个额外属性,我可以在一个页面上使用它(并且可能在其他地方)。真的值得为此创建另一个视图和另一个实体吗?
    • 你想要的现在只是你现有实体在一个页面上使用的额外属性,也可能在另一个页面上使用。未来可能会增加 3 个,可能还有 4 个额外的属性等等。无论如何,我在答案中添加了选项 2。
    • 谢谢@digitaljoel,到目前为止,这似乎是这种情况下的最佳方法。
    • 最后一个想法,如果您有从部门到员工的关联(看起来您可以轻松地给出映射),那么我相信您可以将查询更改为更小的 SELECT new my.package.DepartmentInfo( D, SIZE(D.Employees)) from DEPARTMENT D跨度>
    • @digitaljoel 我还没有尝试过 SIZE() 函数,那将是一个很好的解决方案。您可能需要指定连接“从 DEPARTMENT D 连接 D.EMPLOYEES”以避免多个查询。出于同样的原因,我会避免使用 Select (D, COUNT(*)) 方法...
    【解决方案3】:

    您可以使用“new”语法在您的 QL 中创建任何对象 - 您的类只需要一个构造函数来获取查询返回的值。

    例如,对于像 DepartmentEmployeeCount 这样的类,带有构造函数:

    public DepartmentEmployeeCount(String departmentName, Integer employeeCount)
    

    你可以使用 QL 之类的东西:

    SELECT NEW DepartmentEmployeeCount(D.DEPARTMENT_NAME, count(E.id)) from Department D left join D.employees E GROUP BY D.DEPARTMENT_NAME
    

    或者,如果您只是选择计数(*),您可以简单地将查询结果转换为数字。

    或者,要在没有 DepartmentEmployeeCount 类的情况下执行相同操作,您可以省略 NEW,因此:

    SELECT D.DEPARTMENT_NAME, count(E.id)    
    

    这将返回一个List<Object[]>,其中每个列表项是一个包含 2 个元素的数组,departmentName 和 count。

    要在 cmets 中回答您稍后的问题,要填充 Department 的所有字段以及临时的 employeeCount 字段,一个建议是进行 2 次查询。这仍然比您的原始查询(每个员工计数的子选择)更有效。

    所以一个查询来读取部门

    SELECT D from Department D
    

    给你一个List<Department>

    然后第二个查询返回一个临时数组:

    SELECT D.DEPARTMENT_ID, count(E.id) from Department D left join D.employees E GROUP BY D.DEPARTMENT_ID
    

    给你一个带有 DEPARTMENT_ID 的List<Object[]> 并计入其中。

    然后您使用第二个列表来更新您的第一个列表中的瞬态计数属性。 (您可以尝试选择 Map 以简化此查找,但我认为这是 Hibernate 的一项功能)。

    【讨论】:

    • 感谢@MattR 的回答。此解决方案可能适用于这个简单的示例。但是如果(a)DEPARTMENT 表除了 NAME 之外还有许多其他列,我显然不想在构造函数中列出所有列,并且(b)我仍然希望将它作为 Department 实体的属性(使用 LAZY fetch type) 可以在其他地方访问以防万一。
    • 好问题,我没有尝试过,但我认为您可以在构造函数中使用实体类(因此您不需要选择所有实体属性)。如果我有机会尝试它,我会更新我的答案......即你可以有一个 DepartmentEmployees 类(比如说),它有一个构造函数 DepartmentEmployees(Department, Integer) 并使用 SELECT new DepartmentEmployees(D, count(E.id) )) - 不是你想要的,但更接近......
    • 好的,我在午饭的时候在想这个,所以更新了一些额外的信息。如果您尝试任何建议或找到更好的解决方案,请反馈
    • 是的,我最初想到的是两个查询。我希望有更优雅的东西,要么是一个查询来加载所有内容,要么是延迟获取属性(可以使用任何一种方法,具体取决于现实生活场景的具体情况)。
    • 如果您使用构造函数,您可以使用一个查询,您可能不希望延迟获取,除非您想让事务/会话一直打开到视图...并且 2 个查询更好超过 (1 + #employees) 个查询 :-)
    猜你喜欢
    • 2011-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-06
    • 2015-06-07
    • 2017-05-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多