我正在使用Spring Boot,Groovy和JPA/Hibernate遷移舊的應用程序。唯一的限制是數據庫模式不能改變,我發現自己有一個奇怪的情況是:一OneOnOne關係:Spring JPA + Hibernate OneToOne關係使n + 1查詢
請看下面的模型設置:
@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
}
我斯波克測試看起來如下:
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]}"
}
}
這種構造成功地產生一個INNER Table 1和表2,注意,即使在恆定的「其中」子句正確解釋之間JOIN。
但是,由於某些奇怪的原因,第一個查詢中返回的每一行都會重新查詢Table2。
,我看到的輸出是:
Hibernate:
select
table10_.key_field_1 as key_field_11_3_0_,
table10_.key_field_2 as key_field_22_3_0_,
table21_.key_id as key_id1_5_1_,
table10_.item_source as item_source3_3_0_,
table10_.sequence_num as sequence_num4_3_0_,
table10_.source_type as source_type5_3_0_,
table21_.field2 as field23_5_1_,
table21_.field3 as field34_5_1_,
table21_.field1 as field15_5_1_
from
table1 table10_
inner join
table2 table21_
on table10_.key_field_2=table21_.key_id
where
table10_.item_source=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
// 500+ more of these
我們可以看到第一個查詢成功返回兩個表中的所有行,它實際上是我在尋找確切的查詢。但是,所有這些不必要的額外查詢都正在執行。
是否有任何理由爲什麼JPA會做這樣的事情,有沒有辦法阻止它?
我有這樣的印象我錯過了一些非常明顯的東西。
在此先感謝您的幫助
更新1
如果我更換
cQuery.multiselect(t1, t2)
爲
cQuery.multiselect(t1.get('id').get('key1'), t1.get('id').get('key2'),
t1.get('fieldX'), t1.get('fieldY'), t1.get('fieldZ'),
t2.get('fieldA'), t2.get('fieldB'), t2.get('fieldC') ...)
它產生完全相同的客棧呃加入查詢並且不再重新查詢Table2。
換句話說,看起來像(至少在這種情況下)我需要明確列出兩個表中的所有字段。不是一個很好的解決方法,因爲它可以非常迅速地爲有很多字段的表格變得非常難看。我想知道是否有辦法檢索所有@Column註釋的字段/獲取者而無需資源到一堆反射的東西?
嗨! 「n + 1查詢問題」是ORM世界中衆所周知的問題。你可以谷歌,並得到無數的解釋。至於修復它,我猜你可以取消註釋@Fetch(FetchMode.JOIN)'。你有什麼理由評論這一點? – jeff
我評論說,因爲它似乎對問題沒有影響。無論使用還是不使用@Fetch,它仍然會提供所有這些額外的查詢我正在檢查JPA/Hibernate代碼中的代碼,由於某種原因,我還不太明白,JPA似乎認爲左連接中的實體尚未被檢索並導致「secondPass」代碼被觸發。 ...即使這樣的實體已經存在於結果中作爲「表2」實例。 –
所以我用HQL查詢中的一個hibernate「join fetch」來解決這個問題。 '從個人p加入fetch p.children'。這隻會執行1個查詢。我認爲註釋會提供類似的功能,但可能由於通過JPA而沒有被使用? – jeff