2016-08-21 24 views
4

具有以下簡化實體:如何避免1 + n數據庫調用Hibernate中的雙向可選一對一關聯?

@MappedSuperclass 
public abstract class AbstractEntity implements Serializable { 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    protected Long id; 
} 

@Entity 
@Table(name = "t_invoice") 
public class Invoice extends AbstractEntity { 
    @OneToOne(optional = false, fetch = FetchType.EAGER) 
    @JoinColumn(name = "order_id") 
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) 
    private Order order; 
} 

@Entity 
@Table(name = "t_order") 
public class Order extends AbstractEntity { 
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) 
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) 
    @SortNatural 
    private SortedSet<OrderLine> orderLines = new TreeSet<>(); 

    @OneToOne(optional = true, mappedBy = "order", fetch = FetchType.EAGER) 
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) 
    private Invoice invoice; 
} 

和以下使用庫彈簧數據

public interface InvoiceRepository extends JpaRepository<Invoice, Long> { 
    List<Invoice> findDistinctByInvoiceDateBetween(LocalDate from, LocalDate until); 
} 

使用庫方法擷取發票時1 + N SQL語句作爲日誌中被示出執行:

SELECT DISTINCT i.id, ... FROM t_invoice i WHERE i.invoice_date BETWEEN ? AND ?; 
SELECT i.id, ... FROM t_invoice i WHERE i.order_id = ?; 
SELECT i.id, ... FROM t_invoice i WHERE i.order_id = ?; 
... n 

this SO我明白,當有一對一的可選聯想離子,Hibernate需要進行n次數據庫調用,以確定訂單中的可選發票是否爲空。 讓我感到困惑的是,Hibernate已經在最初的查詢中提取了有問題的發票,那麼爲什麼它不會使用已獲取的發票中的數據呢?

我也嘗試通過使用@NamedEntityGraph和@NamedSubgraph來按順序填充發票以避免n次調用。

因此現在發票實體的樣子:

@Entity 
@NamedEntityGraph(
     name = Invoice.INVOICE_GRAPH, 
     attributeNodes = { 
       @NamedAttributeNode(value = "order", subgraph = "order.subgraph") 
     }, 
     subgraphs = { 
       @NamedSubgraph(name = "order.subgraph", attributeNodes = { 
         @NamedAttributeNode("invoice"), 
         @NamedAttributeNode("orderLines") 
       }), 
     } 
) 
@Table(name = "t_invoice") 
public class Invoice extends AbstractEntity { 
@OneToOne(optional = false, fetch = FetchType.EAGER) 
    @JoinColumn(name = "order_id") 
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) 
    private Order order; 
} 

,並在倉庫中的方法是這樣的:

@EntityGraph(value = Invoice.INVOICE_GRAPH, type = EntityGraph.EntityGraphType.LOAD) 
List<Invoice> findDistinctByInvoiceDateBetween(LocalDate from, LocalDate until); 

但它仍然使n個數據庫調用,即使第一SQL SELECT子句包含發票數據兩次,你可以看到:

SELECT DISTINCT 
    invoice0_.id      AS id1_13_0_, 
    order1_.id      AS id1_14_2_, 
    orderlines4_.id     AS id1_15_4_, 
    invoice5_.id      AS id1_13_5_, 
    invoice0_.created     AS created2_13_0_, 
    order1_.created     AS created2_14_2_, 
    orderlines4_.created    AS created2_15_4_, 
    invoice5_.created     AS created2_13_5_, 
FROM t_invoice invoice0_ ... more join clausules ... 
WHERE invoice0_.order_id = order1_.id AND (invoice0_.invoice_date BETWEEN ? AND ?) 

所以現在我wonderi如何避免n次額外的電話按順序填充發票?

回答

3

據我所知,有一對一的可選關聯時,Hibernate需要使n個數據庫調用,以確定是否爲了可選的發票爲空或不是

是。更精確地說,hibernate不支持可選的ToOne關聯的懶惰,所以它總是會加載關聯的數據。

讓我困惑的是,Hibernate已經在最初的查詢中提取了問題的發票,那麼爲什麼它不會使用已獲取的發票中的數據呢?

Hibernate沒有意識到它已經加載了該發票。要做到這一點,它要麼必須通過它們的order_id來保存發票對象的地圖,要麼爲相互OneToOne關聯進行特殊處理。 OneToOne協會很少,它沒有這種處理。

這可以通過以下任一途徑來解決:

  • 僅該協會的一端地圖,並使用查詢在其他方向導航(即當你想要的發票訂單,做「select invoice from invoice where invoice.order =?」)
  • 映射關聯的兩端,但使可選結束主映射,即將外鍵從訂單移動到發票。那樣的話,訂單的發票是通過主鍵來引用的,哪個hibernate將足夠聰明以便首先在持久化上下文中查找,並且發票的訂單是懶惰的,所以如果這樣的話hibernate只會發出一個冗餘查詢財產實際上被訪問。

哪一個是你的問題的一個更好的解決方案取決於對數據進行操作的其他查詢。一般來說,我傾向於第一種選擇,因爲不得不查詢未映射的數據對於程序員來說更容易理解,而不是爲了讓JPA無法加載隱式請求的數據而採用神祕的技巧。

相關問題