[英]Add a Group by to a Hibernate Criteria-Query without Projection
我有一个 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”。 :(
有什么想法吗?
你有没有试过使用这样的东西?
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();
您可以通过类的反射动态添加属性。
这会像这样创建select distinct prop1,prop2,prop3,prop4 from yourClass
我没有包括DetachedCriteria dc
因为这无关紧要。
GROUP BY WITHOUT PROJECTION:这是不可能的,因为它有道理,在您可能会发现的许多答案中,但大多数人不想使用投影,因为它要求他们投影每个属性,但要求是 bean 必须是预计。 (并作为结果返回)。 在下面的示例中,我尝试将所需的bean
project
为结果对象。
我相信我通过一些技巧取得了相同的结果,首先我试图在没有投影的情况下应用 group by 但我没有找到解决方案,所以我不得不依赖投影。
这是我想要实现的
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.*
成为我的实体 bean 的Parent
类,我希望它是唯一的,一种方法是在 Set 中获取结果列表,但由于多种原因,我不喜欢这种方式:)
所以我从Child.class
而不是Parent.class
创建了一个 Criteria ,这个技巧对我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;
}
}
可以编写 Hibernate 可以用来返回实体的实际 SQL 查询。 因此,如果您确实需要,您可以绕过 HQL 并在其中使用 GROUP BY 准确编写您想要的查询。
有关详细信息,请参见此处。
例如,您可以在 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 的所有属性的简写语法
不按您想要的方式进行分组的主要问题是,在某些 DBMS(如 Oracle)中它不起作用,Oracle 将返回错误。
如果您对选择进行分组,则必须按您选择的所有非聚合字段进行分组。 例如 MySQL 没有这个限制。
我一直在使用的方法是仅选择 id 作为 groupProperty 投影,其中包含所有过滤器、排序和结果数量限制。 然后使用这些检索到的 id 执行其他查询过滤。 这样,实现将独立于 DBMS。
我使用的解决方法是添加 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 上测试
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.