将Group by添加到Hibernate Criteria-Query,无需Projection

时间:2010-01-09 23:20:50

标签: java hibernate group-by criteria

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

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

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

有什么想法吗?

5 个答案:

答案 0 :(得分:2)

你有没有试过这样的东西?

    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,因为那是无关紧要的。

答案 1 :(得分:1)

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

我已经通过一些技巧获得了相同的结果,我相信,首先我试图在没有投影的情况下应用分组,但我找不到解决方案,所以我必须依赖于投影。

这是我想要实现的目标

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;
    }
}

答案 2 :(得分:0)

可以编写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。}简写语法

答案 3 :(得分:0)

在没有预测的情况下进行分组的主要问题是,在某些DBMS(如Oracle)中它不起作用,Oracle将返回错误。

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

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

答案 4 :(得分:0)

我使用的解决方法是将sqlRestriction(“ TRUE GROUP BY this_.some_field”)添加为最后一个条件构建器调用。查询生成器将不会发出任何警告,而只会将其放在WHERE子句的末尾,该子句将最终显示为“其中a = 1和b = 2 ....并由this_.some_field进行真正分组” 不要忘记使用“ this_”前缀按根实体字段进行分组,否则,您可以启动应用程序,并查看查询(如果要使用分组的联接表属性)中别名的命名方式。

这种方法有助于解决由于联接而导致的n + 1行查询问题,并将完全重构延迟到可用时间为止。

在Hibernate 3.4 + MySQL 5.6上进行了测试