休眠复杂1 + N问题将同一对象的多个实例获取到结果集

时间:2018-10-23 12:31:45

标签: java hibernate jpa join fetch

我正在数据库中运行一个现实生活场景,因此,如果有建议的话,我没有任何空间可以更改其结构。这是有问题的结构:

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个查询。 Set中的一个是主要实体,每个实体C N个。所以我想下一步就是加入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 "

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

问题2是,如果我在新联接中使用FETCH,那么我仍然没有得到我的1查询,现在我没有得到确切的A的多个实例,而是某种带有处理程序属性的Wrappers的多个实例。对A的引用被误认为是EntityA _ $$ _ jvstbc0_4 @。

仅是为了对数据库结构有一些了解,我非常确定,此架构是从多对多关系开始的,查找表是EntityA和EntityC之间的EntityB。我可能会尝试使用EntityC上的JoinTable在EntityB的partitionKey和id上进行联接来解决此问题,而EntityA的分区键及其ID可以映射到EntityB上。但是我对这种解决方案不是很希望,因为随着时间的推移,EntityB已被其他列污染,需要选择这些列,而且我不确定该怎么做。

更新1 :我可以看到,当将连接FETCH用于cs时,它将使用必要的列(即填充cs子级)来扩充结果SQL选择。手动运行查询,我正确地获得了作为行的子项的总和。这在SQL上很有意义,但是休眠状态应该已经能够根据其属性聚合其他行。没有联接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查询问题,我无法摆脱。

0 个答案:

没有答案