java - JPA Criteria Query - How to Avoiding Duplicate Joins -


i need make criteria query lot of conditional joins , clauses, in such cases code tends become complex , produces duplicate joins.

for instance have following structure of tables , jpa entities :

account       account_id       account_type   person       name       age       account_id ( fk account )        address_id ( fk address )   address       address_id       location       country 

so assuming m using static metamodel implementation applying criteria queries.

this example of wrong code can generate duplicate joins:

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(); 

the code above generate sql mutiples joins of same table :

 select         account0_.account_id account1_2_,         account0_.account_type account2_2_             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             person3_.name=?         , person4_.age>18         , address6_.country=? 

a simple solution keep instances of joins reuse in multiples predicates :

   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")       )      ) 

ok , works , real complex code several tables , conditional joins codes tends turn spaghetti code ! believe me !

what better way avoid ?

a suggestion avoid use builder class encapsulate joins , see below.

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;         }   } 

the “ace in hole” lazy loads each required join instance, avoid duplicate joins , simplify navigation process.

finally, call builder below :

accountcriteriabuilder criteriabuilder = new accountcriteriabuilder(em.getcriteriabuilder()); typedquery<account> query = em.createquery(criteriabuilder.buildquery()); list<account> result = query.getresultlist(); 

enjoy :)


Comments