Hibernate条件使用FetchType.EAGER多次返回子项

w6mmgewl  于 2022-12-27  发布在  其他
关注(0)|答案(8)|浏览(160)

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

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

这很有效,结果也如预期。
现在我的问题是为什么当我显式地将fetch类型设置为EAGER时,Order会在结果列表中出现多次?

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

我必须如何更改我的条件代码才能在新设置下达到相同的结果?

bq8i3lrv

bq8i3lrv1#

如果我正确理解了您的配置,这实际上是预期的行为。
在任何结果中都会得到相同的Order示例,但由于现在要使用OrderTransaction执行连接,因此它必须返回与常规sql连接相同数量的结果
所以实际上它 * 应该 * 出现多次。这是解释得很好的作者(加文金)自己在这里:它既解释了原因,也解释了如何仍然获得不同的结果
在Hibernate [常见问题][2]中也提到:

    • 对于为集合启用了外连接提取的查询,Hibernate不会返回非重复结果**(即使我使用了distinct关键字)?首先,你需要理解SQL以及外连接在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个元素。所有的Order类型。Hibernate只会创建5个Order示例,但是SQL结果集的副本会作为对这5个示例的重复引用而保留。如果你不理解最后一句话,你需要阅读Java以及Java堆上的示例和对这样一个示例的引用之间的区别。
(Why左外连接?如果您有一个没有行项目的附加订单,结果集将是16行,右侧填充NULL,行项目数据用于其他订单。即使订单没有行项目,您也需要订单,对吗?如果没有,请在HQL中使用内连接提取)。
Hibernate默认情况下不会过滤掉这些重复引用,有些人(不是你)实际上想要这个,你怎么能过滤掉它们呢?
就像这样:

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

ix0qys7i2#

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

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
4dbbbstv

4dbbbstv3#

尝试

@Fetch (FetchMode.SELECT)

例如

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

}

tct7dpnv

tct7dpnv4#

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

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

y3bcpkx15#

使用Java 8和Streams,我在我的实用方法中添加了以下返回语句:

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

流删除重复的速度非常快。我在我的Entity类中使用注解,如下所示:

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

我想在我的应用程序中,如果我需要数据库中的数据,最好在方法中使用会话。当我完成时,关闭会话。当然,设置我的实体类使用最简单的获取类型。我去重构。

lrl1mhuk

lrl1mhuk6#

我在获取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
1.使用仅具有字段"meal"和类型LOAD的EntityGraph,这将加载声明的角色(EAGER和BatchSize = 200以防止N +1问题):
最终溶液:

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

更新:对于Spring Boot 3.0(JPA 3.1),它仅适用于EntityGraphType.FETCH

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

fcy6dtqo7#

而不是使用黑客像:

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

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

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

这修改了所得到的SQL查询,因此在其中具有DISTINCT

c0vxltue

c0vxltue8#

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

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

相关问题