Hibernate Criteria 使用 FetchType.EAGER 多次返回子级

我有一个 Order类,它有一个 OrderTransactions列表,我使用一对多 Hibernate 映射来映射它,如下所示:

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;
}

这些 Order还有一个字段 orderStatus,用于根据以下条件进行过滤:

public List<Order> getOrderForProduct(OrderFilter orderFilter) {
Criteria criteria = getHibernateSession()
.createCriteria(Order.class)
.add(Restrictions.in("orderStatus", orderFilter.getStatusesToShow()));
return criteria.list();
}

这种方法是有效的,结果也如预期的那样。

现在,我的问题是: 为什么当我显式地将提取类型设置为 EAGER时,Order会多次出现在结果列表中?

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;
}

如何更改 Criteria 代码以使新设置达到相同的结果?

82589 次浏览

如果我正确理解了您的配置,这实际上是预期的行为。

在任何结果中都可以得到相同的 Order实例,但是由于现在您正在使用 OrderTransaction进行连接,因此它必须返回常规 sql 连接将返回的相同数量的结果

所以实际上它出现了很多次。这是由作者(加文 · 金)自己解释得很好的: 它既解释了原因,也解释了如何仍然得到不同的结果


在 Hibernate [ FAQ ][2]中也提到:

对于集合 启用了外部连接获取的查询,Hibernate 不会返回不同的结果(即使我使用了 keyword)? First, you need to understand SQL and how OUTER JOINs work 如果您不能完全理解和理解外部连接 SQL, do not continue reading this FAQ item but consult a SQL manual or 否则您将无法理解下面的解释 你会在 Hibernate 论坛上抱怨这种行为。

可能返回相同的重复引用的典型示例 订购对象:

List result = session.createCriteria(Order.class)
.setFetchMode("lineItems", FetchMode.JOIN)
.list();

<class name="Order">
...
<set name="lineItems" fetch="join">

List result = session.createCriteria(Order.class)
.list();
List result = session.createQuery("select o from Order o left join fetch o.lineItems").list();

所有这些示例都生成相同的 SQL 语句:

SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID

想知道为什么会有副本吗? 看看 SQL 结果集, Hibernate 不会将这些副本隐藏在外部的左侧 连接的结果,但返回驱动表的所有副本。如果 数据库中有5个订单,每个订单有3个行项目, 结果集将是15行 将有15个元素,都是 Order 类型。只有5个 Order 实例将 be created by Hibernate, but duplicates of the SQL resultset are 保存为对这5个实例的重复引用 理解最后一句话,你需要阅读 Java 和 Java 堆上的实例与对 这样的例子。

(为什么是左外联接? 如果您有一个没有行的额外订单 items, the result set would be 16 rows with NULL filling up the right 侧,其中行项数据用于其他订单。您需要订单 even if they don't have line items, right? If not, use an inner join 把你的 HQL 带回来)。

Hibernate 默认情况下不会过滤掉这些重复的引用。 有些人(不是你)真的想要这个,你怎么过滤掉他们呢?

像这样:

Collection result = new LinkedHashSet( session.create*(...).list() );

In addition to what is mentioned by Eran, another way to get the behavior you want, is to set the result transformer:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

试试看

@Fetch (FetchMode.SELECT)

比如说

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Fetch (FetchMode.SELECT)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;

}

不要使用 List 和 ArrayList,而是 Set 和 HashSet。

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public Set<OrderTransaction> getOrderTransactions() {
return orderTransactions;
}

使用 Java8和 Streams,我在实用程序方法中添加如下 return 语句:

return results.stream().distinct().collect(Collectors.toList());

流非常快速地删除重复。我在实体类中使用注释如下:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "STUDENT_COURSES")
private List<Course> courses;

我认为是在我的应用程序中使用会话的方法,我需要数据库中的数据。结案陈词。当然设置我的实体类使用容易获取类型。我去重构。

我有同样的问题获取2个相关的集合: 用户有2个角色(集)和2餐(列表)和餐是重复的。

@Table(name = "users")
public class User extends AbstractNamedEntity {


@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
@ElementCollection(fetch = FetchType.EAGER)
@BatchSize(size = 200)
private Set<Role> roles;


@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
@OrderBy("dateTime DESC")
protected List<Meal> meals;
...
}

DISTINCT 没有帮助(DATA-JPA 查询) :

@EntityGraph(attributePaths={"meals", "roles"})
@QueryHints({@QueryHint(name= org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")}) // remove unnecessary distinct from select
@Query("SELECT DISTINCT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);

最后我找到了两个解决方案:

  1. 更改列表为 LinkedHashSet
  2. Use EntityGraph with only field "meal" and type LOAD, which load roles as they declared (EAGER and by BatchSize=200 to prevent N+1 problem):

Final solution:

@EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD)
@Query("SELECT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);

应用外部连接并带来重复的结果,这听起来不是一个很好的行为。剩下的唯一解决方案是使用流对结果进行过滤。感谢 java8提供了更简单的过滤方法。

return results.stream().distinct().collect(Collectors.toList());

而不是使用诸如:

  • Set而不是 List
  • criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

它不会修改您的 sql 查询,我们可以使用(引用 JPA 规范)

q.select(emp).distinct(true);

which does modify the resulting sql query,thus having a DISTINCT in it.