[英]Spring JPA + Hibernate OneToOne relationships making n+1 queries
我使用Spring Boot,Groovy和JPA / Hibernate來遷移舊的應用程序。 唯一的限制是不能更改數據庫模型,我發現自己有一個奇怪的情況,其中OneOnOne關系:
請看下面的模型設置:
@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]}"
}
}
此配置在Table1和Table2之間成功生成INNER JOIN,注意甚至正確解釋“where”子句上的常量。
但是由於某些奇怪的原因,在第一個查詢中返回的每一行都會重新查詢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注釋字段/ getter而無需資源到一堆反射內容?
我想我已經擁有了!
@JoinFormula:
Table2中的主鍵是INT,Table1中用作FK的字段是String(我完全錯過了!duh!)。 因此,解決方案是以下列形式應用@JoinFormula而不是@JoinColumn:
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL) @JoinColumnsOrFormulas([ @JoinColumnOrFormula(formula=@JoinFormula(value='CAST(key_field_2 AS INT)')) ]) @NotFound(action=NotFoundAction.IGNORE) Table2 table2
這奇怪地返回一個List <Object []> List的每個項包含一個包含2個元素的數組:Table1的一個實例和Table2的一個實例。
加入Fetch:
根據您的建議,我在查詢中添加了“join fetch”,所以它看起來像:
select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC'
這會導致Hibernate正確返回List <Table1>
無論是單獨使用@JoinFormula還是使用@JoinFormula +“join fetch”,hibernate都會停止生成n + 1個查詢。
調試Hibernate代碼我發現它在第一次使用連接查詢查詢數據庫時正確地在Session中檢索並存儲兩個實體,但PK和FK數據類型之間的差異導致Hibernate再次重新查詢數據庫,在第一個查詢中檢索到的每一行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.