2017-01-18 60 views
0

我正在使用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註釋的字段/獲取者而無需資源到一堆反射的東西?

+0

嗨! 「n + 1查詢問題」是ORM世界中衆所周知的問題。你可以谷歌,並得到無數的解釋。至於修復它,我猜你可以取消註釋@Fetch(FetchMode.JOIN)'。你有什麼理由評論這一點? – jeff

+0

我評論說,因爲它似乎對問題沒有影響。無論使用還是不使用@Fetch,它仍然會提供所有這些額外的查詢我正在檢查JPA/Hibernate代碼中的代碼,由於某種原因,我還不太明白,JPA似乎認爲左連接中的實體尚未被檢索並導致「secondPass」代碼被觸發。 ...即使這樣的實體已經存在於結果中作爲「表2」實例。 –

+0

所以我用HQL查詢中的一個hibernate「join fetch」來解決這個問題。 '從個人p加入fetch p.children'。這隻會執行1個查詢。我認爲註釋會提供類似的功能,但可能由於通過JPA而沒有被使用? – jeff

回答

0

我想我已經明白了!

  1. @JoinFormula:

    表2中的主鍵是INT和用作FK爲String Table 1中的字段(我完全錯過了咄!)。因此,對於該溶液中的形式來應用@JoinFormula代替@JoinColumn的:

    @OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL) 
    @JoinColumnsOrFormulas([ 
        @JoinColumnOrFormula([email protected](value='CAST(key_field_2 AS INT)')) 
    ]) 
    @NotFound(action=NotFoundAction.IGNORE) 
    Table2 table2 
    

    這奇怪返回一個List <對象[] >列表的每個項目包含2個元素的數組:Table1的一個實例和Table2的一個實例。

  2. 連接抓取:

    按照你的建議,我補充說:「加入取」到查詢,所以它看起來像:

    select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC' 
    

    這導致Hibernate正確地返回一個列表<表1 >

單獨使用@JoinFormula或@JoinFormula +「連接讀取」hibernate停止生成n + 1查詢IES。

調試Hibernate代碼我發現在第一次使用連接查詢查詢數據庫時,它會正確地檢索並存儲Session中的兩個實體,但PK和FK數據類型之間的差異會導致Hibernate重新查詢數據庫再次,在第一個查詢中檢索每行一次。