我正在数据库中运行一个现实生活场景,因此,如果有建议的话,我没有任何空间可以更改其结构。这是有问题的结构:
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查询问题,我无法摆脱。