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

这工作,结果如预期。

现在这是我的问题 :为什么,当我明确地设置获取types为EAGER ,是否在结果列表中出现多次?

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

我将如何更改我的标准代码以达到与新设置相同的结果?

这实际上是预期的行为,如果我正确地理解你的configuration。

你在任何结果中都得到相同的Order实例,但是现在你正在用OrderTransaction做一个连接,它必须返回相同数量的结果,一个普通的sql连接将返回

所以实际上它应该多次。 作者(Gavin King)本人在这里解释得非常好:这既解释了原因,又解释了如何得到明显的结果


在Hibernate FAQ中也提到:

Hibernate不会为一个集合启用外连接抓取的查询返回不同的结果 (即使我使用了distinct关键字)? 首先,您需要了解SQL以及OUTER JOIN在SQL中的工作方式。 如果您不完全理解和理解SQL中的外连接,请不要继续阅读这个FAQ项目,而是参考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个元素,所有types为Order。 Hibernate只会创build5个Order实例,但是SQL结果集的重复被保存为这5个实例的重复引用。 如果你不明白这最后一句话,你需要阅读Java和Java堆上的实例和对这样的实例的引用之间的区别。

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

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

喜欢这个:

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

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

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

使用Java 8和Streams我在我的实用方法中添加了这个返回值:

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

stream快速删除重复。 我在我的实体类中使用注释如下所示:

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

我认为是在我的应用程序使用会话的方法,我需要从数据库中的数据bether。 当我完成时,Closse会话。 Ofcourse设置我的实体类使用leasy获取types。 我去重构。

我有同样的问题来获取2个相关的集合:用户有2个angular色(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不起作用(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); 

最后我find了两个解决scheme:

  1. 将列表更改为LinkedHashSet
  2. 使用EntityGraph只有字段“用餐”和typesLOAD,加载angular色,因为他们声明(EAGER和BatchSize = 200,以防止N + 1问题):

最终解决scheme

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

更新:除非javadoc for org.springframework.data.jpa.repository.EntityGraph.EntityGraphType#FETCH

由实体图的属性节点指定的属性被视为FetchType.EAGER,未指定的属性被视为FetchType.LAZY

这种types的angular色也被提取。