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:
- 将列表更改为LinkedHashSet
- 使用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色也被提取。