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
Post a Comment