java - 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 查询问题。
解决方案
推荐阅读
- r - R 错误:C 堆栈使用太接近小数据集的限制
- git - git merge 是否考虑合并文件的时间戳?
- laravel - Docker web 和 apis 拒绝连接
- spring-boot - Gradle 构建失败 - 带有 lombok 的 Spring Boot 项目
- javascript - 超出 Javascript Recursive Floodfill 最大调用堆栈
- php - 在 twig 项目中发现的这种语法背后的想法是什么?
- javascript - React/Meteor - 在 Autoform 钩子中调用 this.setState
- php - php mail() html - 以纯文本形式发送
- spring-webflow - Spring WebFlow 在代码执行方面是如何工作的?
- kubernetes - 在 Kubernetes 的 deployment.yaml 中定义 2 个端口