【问题标题】:Add a Group by to a Hibernate Criteria-Query without Projection将 Group by 添加到 Hibernate Criteria-Query 而不进行投影
【发布时间】:2011-01-03 08:52:19
【问题描述】:

我有一个 Criteria-Query,它连接第二个表 B,以从表 A 中选择实体。问题是,此查询多次返回表 A 中的一些实体。但我需要结果是不同的。

使用 Criteria.DISTINCT_ROOT_ENTITY 是没用的,因为这会过滤掉 SQL-Query 执行后的多次出现。因此,当我将结果限制为 20 个命中时,我最终只有 4 个匹配我的查询,尽管有更多条目。

在纯 SQL 中,我只需将“GROUP BY ID”添加到查询中,一切都很好,因为表 B 的连接仅用于从表 A 中选择实体。但是使用 Criteria-API 我不能做这个。添加“GROUP BY”的唯一方法是使用投影。但是,我最终得到的是标量值,而不是我的类的真实实例。使用 SQL 限制也不起作用,因为 hibernate 在我的“GROUP BY”子句之后添加了一个虚假的“1=1”。 :(

有什么想法吗?

【问题讨论】:

  • GROUP BY,顾名思义,涉及数据的聚合,这就是为什么需要投影。也许如果您添加了更多表的详细信息,以及您希望 Hibernate 生成的 SQL 查询,我们可以提供更好的建议。
  • 我有一个班级活动。这个类有一个日期列表。 (其实这些Date是一个特殊的DateWrapper类,它把Date包装起来,加上一个Id,因为hibernate此刻不能加入值类型的集合)。我想查询事件并查找在 x 和 y 之间具有一个或多个事件的所有事件。当我抓住由 Criteria-API 生成的 SQL 查询并添加一个“GROUP BY id”时,它完全符合我的要求。但是我找不到任何方法来说服 Hibernate 添加 GROUP BY!

标签: java hibernate group-by criteria


【解决方案1】:

你试过用这样的东西吗?

    ICriteria criteria = dc.GetExecutableCriteria(RepositoryInstance.Session)
            .SetProjection(Projections.distinct(Projections.projectionList()
                    .add(Projections.property("Prop1"), "Prop1")
                    .add(Projections.property("Prop2"), "Prop2")
                    .add(Projections.property("Prop3"), "Prop3")
                    .add(Projections.property("Prop4"), "Prop4")));
    result = criteria.List();

您可以通过类的反射动态添加属性。

这样创建 SQl:select distinct prop1,prop2,prop3,prop4 from yourClass

我没有包含DetachedCriteria dc,因为这无关紧要。

【讨论】:

    【解决方案2】:

    GROUP BY WITHOUT PROJECTION:这是不可能的,因为它是有道理的,在你可能找到的许多答案中,但大多数人不想使用投影,因为它要求他们每一个都投影属性,但要求是必须投影一个 bean。 (并作为结果返回)。在下面的示例中,我尝试将project 所需的bean 作为结果对象。

    我相信我用一点技巧也达到了同样的结果,首先我试图在没有投影的情况下应用 group by,但我没有找到解决方案,所以我不得不依赖 Projection。

    这是我想要实现的目标

    select p.* FROM parent p INNER JOIN child c ON p.id_parent=c.id_father
    WHERE c.child_name like '%?%' AND p.parent_name like '%?%' 
    group by p.id_parent
    

    在 Java 代码中,我希望 p.* 成为 Parent 类,这是我的实体 bean,我希望它是唯一的,一种方法是在 Set 中获取结果列表,但由于多种原因我不喜欢这种方式:)

    所以我从Child.class 而不是Parent.class 创建了一个标准,这个技巧对我有用。

    Criteria c = session.createCriteria(Child.class,"c");// starting from Child
        c.add(Restrictions.like("childName",   "abc", MatchMode.ANYWHERE));
        c.createAlias("parent", "p"); //remember parent is an attribute in Child.class
        c.add(Restrictions.like("p.parentName",   "xyz", MatchMode.ANYWHERE));
        c.setProjection( Projections.projectionList().add(Projections.groupProperty("parent"))); //projecting parent which is an attribute of Child.class
    
        List<Parent> result = c.list(); //get the result
        for (Parent p: result) {
            System.out.println(p);
        }
    

    如果您仍然不明白这里是我映射的实体 Bean 类。

    package com.mazhar.beans;
    
    import static javax.persistence.GenerationType.IDENTITY;
    
    import java.util.List;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;
    
    @Entity
    @Table(name = "parent")
    public class Parent {
        private Integer idParent;
        private String parentName;
        private List<Child> childs;
    
        @Id
        @GeneratedValue(strategy = IDENTITY)
        @Column(name = "id_parent")
        public Integer getIdParent() {
            return idParent;
        }
        public void setIdParent(Integer idParent) {
            this.idParent = idParent;
        }
    
        @Column(name = "parent_name")
        public String getParentName() {
            return parentName;
        }
        public void setParentName(String parentName) {
            this.parentName = parentName;
        }
    
        @OneToMany(fetch=FetchType.LAZY, mappedBy="parent", cascade=CascadeType.ALL)
        public List<Child> getChilds() {
            return childs;
        }
        public void setChilds(List<Child> childs) {
            this.childs = childs;
        }
    
    }
    

    和我的孩子班

    package com.mazhar.beans;
    
    import static javax.persistence.GenerationType.IDENTITY;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.ManyToOne;
    import javax.persistence.Table;
    
    @Entity
    @Table(name = "child")
    public class Child {
        private Integer idChild;
        private String childName;
        private Parent parent; //this actually we projected in criteria query.
    
        @Id
        @GeneratedValue(strategy = IDENTITY)
        @Column(name = "id_city", unique = true, nullable = false)
        public Integer getIdChild() {
            return idChild;
        }
    
        public void setIdChild(Integer idChild) {
            this.idChild = idChild;
        }
    
        @Column(name = "city_name", nullable = false)
        public String getChildName() {
            return childName;
        }
    
        public void setChildName(String cName) {
            this.childName = cName;
        }
    
        @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
        @JoinColumn(name = "id_father")
        public Parent getParent() {
            return parent;
        }
    
        public void setParent(Parent parent) {
            this.parent = parent;
        }
    }
    

    【讨论】:

    • 这是唯一合法的方法,但是像这样更改查询可能会导致添加不需要的连接或额外的消费方法重构时间。此外,我们可以通过 id 获取根实例的投影属性,并通过提供 id 列表进行 get() 调用。但这些都是解决方法,因为标准构建器中缺少将根实体作为 property() 投影加载到结果地图中的方法。
    【解决方案3】:

    可以编写 Hibernate 用来返回实体的实际 SQL 查询。因此,如果您真的需要,您可以绕过 HQL,并在其中使用 GROUP BY 准确编写您想要的查询。

    详情请见here。

    例如,您可以在 hbm.xml 文件中定义类似这样的查询:

    <sql-query name="exampleQuery">
    <query-param name="param" type="int"/>
    <return alias="x" class="foo.bar.X"/>
    <return alias="y" class="foo.bar.Y"/>
        <![CDATA[
            select {x.*}, {y.*}
            from XEntity x
            inner join YEntity y on y.xId = x.id
            where y.value = :param
        ]]>
    </sql-query>
    

    注意用于选择实体 X 和实体 Y 的所有属性的 {x.} 和 {y.} 简写语法

    【讨论】:

    • 感谢您的回答 :) 但我没有使用 HQL。我正在使用 Criteria-API 并且绑定到它,因为必须动态编译查询!因此,我需要找到一种方法来告诉 Hibernate 按 id 属性对结果进行分组,而不使用 Projections,因为我需要我的类的真实实例作为 reslut,而不是标量值。
    • 也可以通过编程方式指定原生 SQL 查询,而不是如上所示的命名查询 - 尽管不是通过使用条件 API。
    【解决方案4】:

    按照您想要的方式进行分组而不进行投影的主要问题是,在某些 DBMS(如 Oracle)中它不起作用,Oracle 将返回错误。

    如果您对选择进行分组,则必须按您选择的所有非聚合字段进行分组。例如 MySQL 就没有这个限制。

    我一直在使用的方法是仅选择 id 作为 groupProperty 投影,其中包含所有过滤器、排序和结果数量限制。然后使用这些检索到的 id 执行其他查询过滤。这样,实现将独立于 DBMS。

    【讨论】:

      【解决方案5】:

      我使用的解决方法是添加 sqlRestriction("TRUE GROUP BY this_.some_field") 作为最后一个标准构建器调用。查询生成器不会发出任何警告,只会将其放在 WHERE 子句的末尾,该子句最终会变成“where a=1 and b=2 ....and true group by this_.some_field” 不要忘记使用“this_”前缀来按根实体字段分组,或者您可以启动应用程序并查看查询中的别名是如何命名的,如果这是您要用于分组的连接表属性。

      这种方法有助于解决由于连接和延迟完全重构直到时间可用而导致的查询的 n+1 行问题。

      在 Hibernate 3.4 + MySQL 5.6 上测试

      【讨论】:

        猜你喜欢
        • 2017-07-17
        • 2012-01-19
        • 2015-12-12
        • 2015-02-27
        • 1970-01-01
        • 2020-02-22
        • 2011-04-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多