Search code examples
jpaeclipselinkcriteria-api

JPA Criteria API - Populate associations


I'm trying to use the JPA Criteria API to do a very easy query.

I have two entities:

@Entity
@Table(name = "PERSONS")
public class Person implements Serializable
{
   @Id
   @Column(name = "COD_PERSON") 
   private String personCode;

   @Column(name = "NAME")   
   private String name;

   @OneToMany(mappedBy="person")
   List<Address > addresses;

   ... other attributes, getters and setters
}

@Entity
@Table(name = "ADDRESS")
public class Address implements Serializable
{
   private static final long serialVersionUID = 1L;

   @Id
   @Column(name = "COD_ADDRESS")
   private String addressCode;

   @ManyToOne(fetch=FetchType.LAZY, optional=false)
   @JoinColumn(name = "COD_PERSON")
   private Person person;

   @Column(name = "street") 
   private String street;

   @Column(name = "zipCode")    
   private String zipCode;

   ... other attributes, getters and setters
}

We have defined a ManyToOne association in 'Address' entity, and a OneToMany association in 'Person' entity. Both are defined as lazy because of perfomance issues.

Then I would like for example to query people who lives in a certain zip code. And I would like to get people populated with their addresses.

Because we have defined associations as lazy, and because 'addresses' is a OneToMany association, I think I should use "eclipselink.batch" hint.

I have tried with:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> from = query.from(Person.class);
Join<Person, Address> join =  from.join("addresses");
query.select(from);
Predicate predicate = cb.equal(join .get("zipCode"), "28020");
query.where(predicate);
List results = em.createQuery(query).setHint(org.eclipse.persistence.config.QueryHints.BATCH, "p.addresses").getResultList();       

I get all the people who live in a certain zip code, that's ok, but 'addreses' attribute is not populated.

How could I get 'person' entities populated with 'addreses' attribute?

Thanks

THE SOLUTION I HAVE FOUND:

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Person> query = cb.createQuery(Person.class);
    Root<Person> from = query.from(Person.class);
    Join<Person, Address> join =  (Join<Person, Address>) from.fetch("addresses", JoinType.LEFT);
    query.select(from);
    Predicate predicate = cb.equal(join.get("zipCode"), "28020");       
    query.where(predicate);
    List results = em.createQuery(query).getResultList();

What I don't like very much is the casting I have to do. I've tried it with EclipseLink provider. I don't know if it would work with other providers.


Solution

  • You can use the "fetch" method on "Root" class.

    For example,

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery q = cb.createQuery(Order.class);
    Root o = q.from(Order.class);
    o.fetch("items", JoinType.INNER);
    q.select(o);
    q.where(cb.equal(o.get("id"), orderId));
    
    Order order = (Order)this.em.createQuery(q).getSingleResult();
    

    Also, you can check other options from here

    https://www.thoughts-on-java.org/5-ways-to-initialize-lazy-relations-and-when-to-use-them/

    Edit 1:

    You can do fetch join that results in only one join like below

    ...
    Join<Order, OrderItem> join = (Join<Order,OrderItem>)root.<Order,OrderItem>fetch("items");
    ...
    

    Edit 2:

    If you don't want to use casting, you can use entity graphs. But this solution is available since JPA 2.1

    ...
    EntityGraph<Order> fetchGraph = entityManager.createEntityGraph(Order.class);
    fetchGraph.addSubgraph("items");
    TypedQuery<Order> orderQuery = entityManager.createQuery(query);
    orderQuery.setHint("javax.persistence.loadgraph", fetchGraph);
    ...