Java 春季JPA+;Hibernate-OneToOne关系使n+;1查询
我正在使用SpringBoot、Groovy和JPA/Hibernate迁移一个旧的应用程序。唯一的限制是不能更改数据库模型,我发现自己遇到了一个奇怪的情况,即OneOnOne关系: 请查看以下模型设置:Java 春季JPA+;Hibernate-OneToOne关系使n+;1查询,java,hibernate,jpa,groovy,spring-boot,Java,Hibernate,Jpa,Groovy,Spring Boot,我正在使用SpringBoot、Groovy和JPA/Hibernate迁移一个旧的应用程序。唯一的限制是不能更改数据库模型,我发现自己遇到了一个奇怪的情况,即OneOnOne关系: 请查看以下模型设置: @Entity @Table(name='table1') class Table1 { @Id Table1Id id @Column(name='sequence_num') Integer seqNum @Column(name='item_source')
@Entity
@Table(name='table1')
class Table1 {
@Id
Table1Id id
@Column(name='sequence_num')
Integer seqNum
@Column(name='item_source')
String itemSource
@Column(name='source_type')
String sourceType
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumn(name='key_field_2', insertable=false, updatable=false)
@NotFound(action=NotFoundAction.IGNORE)
// @Fetch(FetchMode.JOIN)
Table2 table2
}
@Embeddable
class Table1Id implements Serializable {
@Column(name='key_field_1')
String key1
@Column(name='key_field_2')
String key2
}
@Entity
@Table(name='table2')
class Table2 {
@Id
@Column(name='key_id')
String keyId
@Column(name='field1')
String field1
@Column(name='field2')
String field2
@Column(name='field3')
String field3
}
我的Spock测试如下所示:
def "Try out the JOIN select with Criteria API"() {
given:
CriteriaBuilder cb = entityManager.getCriteriaBuilder()
CriteriaQuery<Object[]> cQuery = cb.createQuery(Object[].class)
Root<Table1> t1 = cQuery.from(Table1.class)
Path<Table2> t2 = t1.get('table2')
Join<Table1, Table2> lanyonLeftJoin = t1.join('table2', JoinType.INNER)
Predicate where = cb.equal(t1.get('itemSource'), 'ABC')
cQuery.multiselect(t1, t2)
cQuery.where(where)
when:
List<Object[]> result = entityManager.createQuery(cQuery).getResultList()
then:
result.each{ aRow ->
println "${aRow[0]}, ${aRow[1]}"
}
}
正如我们所看到的,第一个查询成功地返回了两个表中的所有行,实际上这正是我要查找的查询。然而,所有这些不必要的额外查询都会被执行
JPA为什么会做这样的事情,有什么原因吗?有什么方法可以防止吗
我觉得我在这里遗漏了一些非常明显的东西
提前谢谢你的帮助
更新1 如果我替换
cQuery.multiselect(t1, t2)
为了
它生成完全相同的内部联接查询,并且不再重新查询表2
换句话说,看起来(至少在这种情况下)我需要显式列出两个表中的所有字段。这不是一个很好的解决方法,因为对于有很多字段的表,它可能会很快变得非常丑陋。我想知道是否有一种方法可以检索所有@Column注释的字段/getter,而不必使用一堆反射内容?我想我已经找到了
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumnsOrFormulas([
@JoinColumnOrFormula(formula=@JoinFormula(value='CAST(key_field_2 AS INT)'))
])
@NotFound(action=NotFoundAction.IGNORE)
Table2 table2
这会奇怪地返回一个列表,列表中的每个项目都包含一个由2个元素组成的数组:一个Table1实例和一个Table2实例select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC'
这会使Hibernate正确返回列表@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumnsOrFormulas([
@JoinColumnOrFormula(formula=@JoinFormula(value='CAST(key_field_2 AS INT)'))
])
@NotFound(action=NotFoundAction.IGNORE)
Table2 table2
这会奇怪地返回一个列表,列表中的每个项目都包含一个由2个元素组成的数组:一个Table1实例和一个Table2实例select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC'
这会使Hibernate正确返回列表调试Hibernate代码我发现它在第一次使用join查询DB时正确地检索并在会话中存储了这两个实体,但是PK和FK数据类型之间的差异导致Hibernate再次重新查询DB,在第一次查询中检索的每行一次。Hi!“n+1查询问题”是ORM世界中一个广为人知的问题。你可以通过谷歌搜索得到无数的解释。至于修复它,我想您可以取消注释
@Fetch(FetchMode.JOIN)
。你有什么理由把它评论掉吗?我把它评论掉了,因为它似乎对这个问题没有影响。无论是否使用@Fetch,它仍然会进行所有这些额外的查询。我正在检查JPA/Hibernate代码中的代码,出于某种我还不太了解的原因,JPA似乎认为左侧连接中的实体尚未检索,并导致触发“secondPass”代码。。。。即使这样的实体已经作为“Table2”实例出现在结果中,所以我在一个旧项目中使用hibernate“join fetch”在HQL查询中解决了这个问题从个人p加入获取p.children
。这将只执行一个查询。我原以为注释会提供类似的功能,但可能因为要通过JPA而没有得到使用?嗨!“n+1查询问题”是ORM世界中一个广为人知的问题。你可以通过谷歌搜索得到无数的解释。至于修复它,我想您可以取消注释@Fetch(FetchMode.JOIN)
。你有什么理由把它评论掉吗?我把它评论掉了,因为它似乎对这个问题没有影响。无论是否使用@Fetch,它仍然会进行所有这些额外的查询。我正在检查JPA/Hibernate代码中的代码,出于某种我还不太了解的原因,JPA似乎认为左侧连接中的实体尚未检索,并导致触发“secondPass”代码。。。。即使这样的实体已经作为“Table2”实例出现在结果中,所以我在一个旧项目中使用hibernate“join fetch”在HQL查询中解决了这个问题从个人p加入获取p.children
。这将只执行一个查询。我原以为注释会提供类似的功能,但可能因为要通过JPA而无法使用?