Java Hibernate条件多次返回FetchType.EAGER的子项

Java Hibernate条件多次返回FetchType.EAGER的子项,java,hibernate,Java,Hibernate,我有一个Order类,它有一个OrderTransactions列表,我用一对多Hibernate映射将其映射,如下所示: @OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL) public List<OrderTransaction> getOrderTransactions() { return orderTransactions; } 这是可行的,结果正如预期的那样 现

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

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}
这是可行的,结果正如预期的那样

现在这是我的问题:为什么当我将fetch类型显式设置为
EAGER
时,
顺序
会在结果列表中多次出现

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}
@OneToMany(targetEntity=OrderTransaction.class,fetch=FetchType.EAGER,cascade=CascadeType.ALL)
公共列表getOrderTransactions(){
退货订单交易;
}

如果我正确理解您的配置,如何更改标准代码以达到与新设置相同的结果?

这实际上是预期的行为

您在任何结果中都会得到相同的
Order
实例,但由于现在您正在使用
OrderTransaction
执行联接,因此它必须返回与常规sql联接相同数量的结果

所以实际上它应该出现多次。作者(加文·金)本人对此做了很好的解释: 它既解释了为什么,又解释了如何仍然获得不同的结果


在Hibernate中也提到:

对于为集合启用外部联接获取的查询,Hibernate不会返回不同的结果(即使我使用了不同的 关键词)?首先,您需要了解SQL以及外部联接是如何工作的 在SQL中。如果您不完全理解和理解 SQL,请不要继续阅读此常见问题解答项目,请查阅SQL手册或 辅导的否则,您将无法理解以下解释 你会在Hibernate论坛上抱怨这种行为

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

List result = session.createCriteria(Order.class)
                    .setFetchMode("lineItems", FetchMode.JOIN)
                    .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个元素,所有类型的顺序。只有5个订单实例将被删除 可以由Hibernate创建,但SQL结果集的副本 保留为对这5个实例的重复引用。如果你不 理解最后一句话,您需要通读Java和 Java堆上的实例和对的引用之间的差异 这样的例子

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

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

像这样:

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

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

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
试一试

比如说

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Fetch (FetchMode.SELECT)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;
@OneToMany(targetEntity=OrderTransaction.class,fetch=FetchType.EAGER,cascade=CascadeType.ALL)
@Fetch(FetchMode.SELECT)
公共列表getOrderTransactions(){
退货订单交易;

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

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public Set<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}
@OneToMany(targetEntity=OrderTransaction.class,cascade=CascadeType.ALL)
公共集getOrderTransactions(){
退货订单交易;
}

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

return results.stream().distinct().collect(Collectors.toList());
流删除重复非常快。我在实体类中使用注释,如下所示:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "STUDENT_COURSES")
private List<Course> courses;
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@JoinTable(name=“学生课程”)
私人名单课程;

我认为在我的应用程序中使用session-in方法更好,因为我需要数据库中的数据。当我完成的时候。当然,将我的实体类设置为使用leasyfetch类型。我去重构。

我在获取两个相关集合时遇到了同样的问题:用户有两个角色(集合)和两份膳食(列表),膳食是重复的

@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;
   ...
}
最后,我找到了两种解决方案:

  • 仅将EntityGraph与字段“Fine”和类型LOAD一起使用,按声明加载角色(Earge和by 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);
    

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

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

    而不是使用如下黑客:

    • 设置
      而不是
      列表
    • criteria.setResultTransformer(criteria.DISTINCT\u ROOT\u实体)
    如果不修改sql查询,我们可以使用(引用JPA规范)


    它会修改生成的sql查询,因此在其中有一个
    DISTINCT

    您是否尝试过启用show_sql以查看下面的情况?请同时添加OrderTransaction和Order类代码。\n即使您理解以下解释,您也可能会在Hibernate论坛上抱怨这种行为,因为这是一种愚蠢的行为!没错,汤姆,我忘了加文·金的傲慢态度。他还说,“Hibernate默认情况下不会过滤掉这些重复引用。有些人(不是你)真的想要这个,当人们真的想要这个的时候,我会感兴趣的。@TomAnderson是的,没错。为什么会有人需要这些复制品?我问这个纯粹是出于好奇,因为我不知道。。。您可以自己创建副本,任意多个副本…-)唉。这实际上是Hibernate缺陷,IMHO。我想优化我的查询,所以
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "STUDENT_COURSES")
    private List<Course> courses;
    
    @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;
       ...
    }
    
    @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);
    
    @EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD)
    @Query("SELECT u FROM User u WHERE u.id=?1")
    User getWithMeals(int id);
    
    return results.stream().distinct().collect(Collectors.toList());
    
    q.select(emp).distinct(true);