首页 > 解决方案 > Hibernate 复杂的 1+N 问题将同一对象的多个实例提取到结果集中

问题描述

我在我们的数据库中运行一个真实的场景,因此我没有空间来改变它的结构,以防万一这是作为建议出现的。这是有问题的结构:

EntityA {
    @Id
    .......
    @OneToMany(mappedBy = "xxxx", fetch = FetchType.LAZY)
    private Set<EntityB> entityB;   

    @Column(name = "partition", columnDefinition = "nchar", length = 3)
    private String partitionKey;
}

EntityB {
    @Id
    ..........   
    @Column(name = "partition")
    private String partitionKey; 

    @ManyToOne(fetch = FetchType.LAZY) //EAGER is the default for the to-one
    @JoinColumn(name = "bbbb")
    private EntityA entityA;

    @OneToMany(mappedBy = "EntityCPk.entityB", fetch = FetchType.LAZY)
    private Set<EntityC> entityC;

    @OneToOne(mappedBy = "cccc", cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private EntityD entityD;
}

EntityC {
   @EmbeddedId
   private EntityCPk entityCPk;   

   @Embeddable
   public static class EntityCPk implements Serializable {
       @Column( name = "partition")
       private String partitionKey;    

       @ManyToOne
       @JoinColumn(name = "xxxx")
       private EntityB entityB;

       @ManyToOne
       @JoinColumn(name = "xxxx")
       private EntityE entityE;
   }
}

EntityD {
    @id
    .........
    @MapsId
    @OneToOne
    @JoinColumn(name = "zzzz", columnDefinition = "nchar")
    @PrimaryKeyJoinColumn
    private EntityB entityB;
}

EntityE {
    @id
    .........
    @OneToMany(mappedBy = "yyyPk.entityE")
    private Set<EntityC> entityC;
}

现在的要求是使用连接一次性运行查询并避免 1+N 场景。据我所知,当在存储库中使用 Query 注释以及 FETCH 选项时,我假设 .LAZY 或 EAGER 注释被“覆盖”。所以这就是我所取得的成就(entityXXX 和 entityYYY 不会干扰我们的案例,所以我只提到它们):

第一次尝试在 EntityB.entityC 属性中使用 FetchType.LAZY:

"select a" +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD latest " +            
"where a.aProp in ( :props ) " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "

结果,如预期。我得到 1 个查询,但是由于惰性注释,我没有在 EntityB.entityC 中返回任何集合,当然它也不存在于查询中。如果我将 EntityB.entityC 更改为 FetchType.EAGER,那么我会得到预期的 3 个查询。一个是主要的,N 个是 Set 中的每个实体 C。所以我猜下一步是加入entityC:

第二次尝试:

"select a " +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD as latest " +  
"join bs.entityC as cs " + //please note I am not using FETCH yet          
"where a.aProp in ( :props ) " +
"and c.entityCPk.partitionKey = a.partitionKey " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "

结果出乎意料,我认为这里也有报道。我现在得到的是对同一对象的所有引用的 a(s) 的倍数等于 bs.entityC 数量的总和。因此,如果例如 a-1 -> 有 1-bs -> 有 17 个 cs 并且 a2 -> 有 1-bs -> 有 67 个 cs,那么我最终得到的结果集是 84 个 a 对象都一样!这是问题一。为什么会这样?

问题 2 是,如果我在新加入中使用 FETCH,那么我仍然没有得到我的 1 查询,现在我没有得到 A 的多个实例,而是具有引用 A 的处理程序属性的某种 Wrappers 的多个实例标记为 EntityA_$$_jvstbc0_4@。

只是为了对数据库结构有一些了解,我非常确定这个模式开始是一个多对多关系,查找表是 EntityA 和 EntityC 之间的 EntityB。我可能会尝试使用 EntityC 上的 JoinTable 加入 EntityB 的 partitionKey 和 id 来解决该问题,而 EntityA 具有要映射到 EntityB 的 partitionkey 及其 Id。但是我对这个解决方案不是很有希望,因为随着时间的推移,EntityB 已经被其他需要选择的列污染了,我不确定我该怎么做。

更新 1:我可以看到,当 join FETCH 用于 cs 时,它会使用必要的列来扩充生成的 SQL 选择,即填充 cs 子项。手动运行查询我正确地将子项的总和作为行。这在 SQL 方面是有道理的,但 hibernate 应该能够根据它们的属性聚合额外的行。没有加入 FETCH 就足够了,我只得到等于 a 的数量的行。所以我的第二个问题是我需要以某种方式指示 Hibernate 手动聚合(?)

更新 2:改变策略。我们最好回答以下问题,而不是开始遵循 SQL 逻辑:哪个类/实体将为我们提供我们正在寻找的粒度。在前面的示例中,我们从 EntityA 开始尝试限制其子级以适应我们的预期结果。然而,正如已经指出的那样,这实际上是对象的损坏。您不能“限制”孩子,因为他们都属于实体并获取其中的一个子集,您冒着删除(?)数据的风险。所以方法必须是让我们感兴趣的子对象指向父实体。我们不改变数据。所以这是一个返回正确数量的对象的查询。没有明显的或莫名其妙的多重性:

"select c " +
"from EntityC c " +
"inner join c.EntityCPk.EntityB.EntityD latest  " +
"join latest.EntityB.EntityXXX xxx " +
"join latest.EntityB.EntityYYY yyy " +
"join fetch c.EntityCPk.EntityB  " +
"where latest.EntityB.EntityA.Id in ( :param ) " +
"and latest.EntityB.aField between :paramA and paramB " 

因此,这似乎回答了前面示例的多样性问题,因为每一行都基于通过 -ToOne 关系解析其父对象的“更精细”的子对象。在连接提取中也没有更多危险的别名。只有一个问题。它为 EntityB 引入了一个我无法摆脱的 1+N 查询问题。

标签: javahibernatejpajoinfetch

解决方案


推荐阅读