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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1995080/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 02:15:05  来源:igfitidea点击:

Hibernate Criteria returns children multiple times with FetchType.EAGER

javahibernate

提问by raoulsson

I have an Orderclass that has a list of OrderTransactionsand I mapped it with a one-to-many Hibernate mapping like so:

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

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

These Orders also have a field orderStatus, which is used for filtering with the following Criteria:

这些Orders 也有一个字段orderStatus,用于使用以下标准进行过滤:

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

This works and the result is as expected.

这有效,结果如预期。

Now here is my question: Why, when I set the fetch type explicitly to EAGER, do the Orders appear multiple times in the resulting list?

现在我的问题是:为什么当我将 fetch 类型显式设置为 时EAGEROrders 会在结果列表中出现多次吗?

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

How would I have to change my Criteria code to reach the same result with the new setting?

我必须如何更改我的标准代码才能使用新设置达到相同的结果?

采纳答案by Eran Medan

This is actually the expected behaviour if I understood your configuration correctly.

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

You get the same Orderinstance in any of the results, but since now you are doing a join with the OrderTransaction, it has to return the same amount of results a regular sql join will return

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

So actually it shouldapear multiple times. this is explained very well by the author (Gavin King) himself here: It both explains why, and how to still get distinct results

所以实际上它应该出现多次。作者(Gavin King)自己在这里很好地解释了这一点:它既解释了原因,也解释了如何仍然获得不同的结果



在 Hibernate 中也提到 FAQ常见问题

Hibernate does not return distinct results for a query with outer join fetching enabled for a collection(even if I use the distinct keyword)? First, you need to understand SQL and how OUTER JOINs work in SQL. If you do not fully understand and comprehend outer joins in SQL, do not continue reading this FAQ item but consult a SQL manual or tutorial. Otherwise you will not understand the following explanation and you will complain about this behavior on the Hibernate forum.

Typical examples that might return duplicate references of the same Order object:

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();

All of these examples produce the same SQL statement:

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

Want to know why the duplicates are there? Look at the SQL resultset, Hibernate does not hide these duplicates on the left side of the outer joined result but returns all the duplicates of the driving table. If you have 5 orders in the database, and each order has 3 line items, the resultset will be 15 rows. The Java result list of these queries will have 15 elements, all of type Order. Only 5 Order instances will be created by Hibernate, but duplicates of the SQL resultset are preserved as duplicate references to these 5 instances. If you do not understand this last sentence, you need to read up on Java and the difference between an instance on the Java heap and a reference to such an instance.

(Why a left outer join? If you'd have an additional order with no line items, the result set would be 16 rows with NULL filling up the right side, where the line item data is for other order. You want orders even if they don't have line items, right? If not, use an inner join fetch in your HQL).

Hibernate does not filter out these duplicate references by default. Some people (not you) actually want this. How can you filter them out?

Like this:

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

Hibernate 不会为为集合启用外连接提取的查询返回不同的结果(即使我使用了 distinct 关键字)?首先,您需要了解 SQL 以及 OUTER JOIN 在 SQL 中的工作方式。如果您不完全理解和理解 SQL 中的外连接,请不要继续阅读此常见问题解答项目,而是查阅 SQL 手册或教程。否则你不会理解下面的解释,你会在 Hibernate 论坛上抱怨这种行为。

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

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 行。这些查询的 Java 结果列表将有 15 个元素,所有元素都是 Order 类型。Hibernate 只会创建 5 个 Order 实例,但 SQL 结果集的副本将保留为对这 5 个实例的重复引用。如果您不理解最后一句话,您需要阅读 Java 以及 Java 堆上的实例与对此类实例的引用之间的区别。

(为什么是左外连接?如果您有一个没有订单项的附加订单,结果集将是 16 行,右侧填充 NULL,其中订单项数据用于其他订单。您想要订单,即使他们没有订单项,对吗?如果没有,请在您的 HQL 中使用内部联接提取)。

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

像这样:

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

回答by EJB

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

除了 Eran 提到的,另一种获得你想要的行为的方法是设置结果转换器:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

回答by mathi

try

尝试

@Fetch (FetchMode.SELECT) 

for example

例如

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

}

}

回答by Αλ?κο?

Do not use List and ArrayList but Set and HashSet.

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

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

回答by easyScript

Using Java 8 and Streams I add in my utility method this return statment:

使用 Java 8 和 Streams,我在实用程序方法中添加了此返回语句:

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

Streams remove duplicate very fast. I use annotation in my Entity class like this:

流删除重复非常快。我在我的实体类中使用注释,如下所示:

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

I think is bether in my app to use session in method where I need data from data base. Closse session when I done. Ofcourse set my Entity class to use leasy fetch type. I go to refactor.

我认为最好在我的应用程序中使用会话在我需要来自数据库的数据的方法中。我完成后的闭幕式。当然将我的实体类设置为使用轻松获取类型。我去重构。

回答by Grigory Kislin

I have the same problem to fetch 2 associated collections: user has 2 roles (Set) and 2 meals (List) and meals are duplicated.

我在获取 2 个关联集合时遇到了同样的问题:用户有 2 个角色 (Set) 和 2 份饭菜 (List),而且饭菜是重复的。

@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 doesn't help (DATA-JPA query):

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

At last I've found 2 solutions:

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

  1. Change List to 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):
  1. 将列表更改为 LinkedHashSet
  2. 使用只有字段“meal”和类型 LOAD 的 EntityGraph,它们按照声明加载角色(EAGER 和 BatchSize=200 以防止 N+1 问题):

Final solution:

最终解决方案:

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

回答by Pravin

It doesn't sounds a great behavior applying an outer join and bring duplicated results. The only solution left is to filter our result using streams. Thanks java8 giving easier way to filter.

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

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

回答by Kaustubh

Instead of using hacks like :

而不是使用像这样的黑客:

  • Setinstead of List
  • criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
  • Set代替 List
  • criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

which don't modify your sql query , we can use (quoting JPA specs)

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

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

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

它确实修改了生成的 sql 查询,因此在其中包含一个DISTINCT