簡體   English   中英

Spring JPA + Hibernate OneToOne關系進行n + 1次查詢

[英]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而無需資源到一堆反射內容?

我想我已經擁有了!

  1. @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的一個實例。

  2. 加入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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM