2015-07-21 82 views
2

我需要做很多條件連接和where子句的條件查詢,在這種情況下,代碼往往變得複雜並且可能會產生重複的連接。JPA標準查詢 - 如何避免重複連接

例如我有表和JPA實體的結構如下:

ACCOUNT 
     ACCOUNT_ID 
     ACCOUNT_TYPE 


PERSON 
     NAME 
     AGE 
     ACCOUNT_ID (FK TO ACCOUNT) 
     ADDRESS_ID (FK TO ADDRESS) 

ADDRESS 
     ADDRESS_ID 
     LOCATION 
     COUNTRY 

所以假設讀音字使用靜態元模型執行用於施加標準的查詢。

這是實施例的錯誤代碼,可以產生重複的聯接:

CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    CriteriaQuery<Account> cq = cb.createQuery(Account.class); 

    cq.select(accountRoot).where(
    cb.and(
     cb.equal(accountRoot.join(Account_.person).get(Person_.name),"Roger"), 
     cb.greaterThan(accountRoot.join(Account_.person).get(Person_.age),18), 
     cb.equal(accountRoot.join(Account_.person)         
       .join(Person_.address).get(Address_.country),"United States") 
    ) 
    ) 

    TypedQuery<Account> query = entityManager.createQuery(cq); 
    List<Account> result = query.getResultList(); 

上面的代碼將生成具有mutiples一個SQL連接在同一個表中:

Select 
     account0_.account_id as account1_2_, 
     account0_.account_type as account2_2_ 
    from 
     account account0_ 
    inner join 
     person person1_ 
      on account0_.account_id=person1_.account_id 
    inner join 
     address address2_ 
      on person1_.address_id=address2_.address_id 
    inner join 
     person person3_ 
      on account0_.account_id=person3_.account_id 
    inner join 
     person person4_ 
      on account0_.account_id=person4_.account_id 
    inner join 
     person person5_ 
      on account0_.account_id=person5_.account_id 
    inner join 
     address address6_ 
      on person5_.address_id=address6_.address_id 
    where 
     person3_.name=? 
     and person4_.age>18 
     and address6_.country=? 

一個簡單的解決方案是以保持連接的實例在多重謂詞中重複使用:

Root<Account> accountRoot = cq.from(Account.class); 
    Join<Account,Person> personJoin= accountRoot.join(Account_.person); 
    Join<Person,Address> personAddressJoin = accountRoot.join(Person_.address); 

    cq.select(accountRoot).where(
    cb.and(
     cb.equal(personJoin.get(Person_.name),"Roger"), 
     cb.greaterThan(personJoin.get(Person_.age),18), 
     cb.equal(personAddressJoin.get(Address_.country),"United States") 
    ) 
    ) 

好吧,它的工作原理,但與一個真正複雜的代碼與幾個表和條件連接的代碼往往會變成一個意大利麪代碼!相信我 !

有什麼更好的方法來避免它?

+0

您有多個連接的SQL,因爲你的代碼必須'join'許多重複的調用。你期望會發生什麼?爲實際需要的每個連接調用一次'join',並重用返回的'Join'實例。您已經創建了您需要的兩個'Join'實例 - 只需使用它們即可! – Rob

+0

@Rob對不起,tx您的評論,我昨天只發布了不完整的問題,我修正了它。我知道這是錯誤的,在我的第一次使用標準查詢和加入的經歷中,我犯了這個錯誤,我應該發佈兩個錯誤的代碼並修復。問題已修復!然而,我不認爲答案是錯的,這只是一個建議,以避免它在一個複雜的標準查詢中,我有一個很好的經驗,在我的工作這個戰略,所以我想分享這個解決方案,但好吧,如果它是你的意見。 –

回答

0

建議避免使用構建器類來封裝連接,請參見下文。

public class AccountCriteriaBuilder { 

     CriteriaBuilder cb; 
     CriteriaQuery<Account> cq; 

     // JOINS INSTANCE 
     Root<Account> accountRoot; 
     Join<Account,Person> personJoin; 
     Join<Person,Address> personAddressJoin; 

     public AccountCriteriaBuilder(CriteriaBuilder criteriaBuilder) { 
      this.cb = criteriaBuilder; 
      this.cq = cb.createQuery(Account.class); 
      this.accountRoot = cq.from(Account.class); 
     } 

     public CriteriaQuery buildQuery() { 
      Predicate[] predicates = getPredicates(); 
      cq.select(accountRoot).where(predicates); 
      return cq; 
     } 

     public Predicate[] getPredicates() { 

      List<Predicate> predicates = new ArrayList<Predicate>(); 

      predicates.add(cb.equal(getPersonJoin().get(Person_.name), "Roger")); 
      predicates.add(cb.greaterThan(getPersonJoin().get(Person_.age), 18)); 
      predicates.add(cb.equal(getPersonAddressJoin().get(Address_.country),"United States")); 

      return predicates.toArray(new Predicate[predicates.size()]); 
     } 

     public Root<Account> getAccountRoot() { 
      return accountRoot; 
     } 

     public Join<Account, Person> getPersonJoin() { 
      if(personJoin == null){ 
       personJoin = getAccountRoot().join(Account_.person); 
      } 
      return personJoin; 
     } 

     public Join<Person, Address> getPersonAddressJoin() { 
      if(personAddressJoin == null){ 
       personAddressJoin = getPersonJoin().join(Person_.address); 
      } 
      return personAddressJoin; 
     } 


} 

的「殺手鐗」是每個所需的延遲加載的連接實例,這將避免重複加入,也簡化導航過程。

最後,只需調用建設者象下面這樣:

AccountCriteriaBuilder criteriaBuilder = new AccountCriteriaBuilder(em.getCriteriaBuilder()); 
TypedQuery<Account> query = em.createQuery(criteriaBuilder.buildQuery()); 
List<Account> result = query.getResultList(); 

享受:)

+0

對於看到它運行我有完整的示例實施與H2內存數據庫和嵌入碼頭..只需克隆和運行 https://github.com/dufabricio/hibernate-sample –

+1

我想我不明白它。您的「簡單」解決方案是8行代碼。你的Builder解決方案就像30行代碼一樣,可以做同樣的事情。除非額外的複雜性可以被拉入到許多實體使用的基類中,或者在許多不同的查詢中分期付款,否則無法證明這一點。在我看來,爲了使用模式而使用模式是一種反模式。 – Rob

+0

好的,我同意!這個示例代碼不是真正的spaguetti代碼,我只是創建一個示例來解釋..它不是真實的情況..但是,指出@Rob我會嘗試使樣本更加類似於我的真實代碼,以證明它的正確性更好.. 它是互聯網的真棒部分..言論自由! –