Search code examples
eclipselinkcriteria-apimulti-select

JPA criteriaquery tuple onetomany join


I need to do a join with criteria builder and then to get result of Tuple(Object[]) type. First Tuple element should be Preke (and this works correctly) and second Tuple element should be a List<PrekeTiekejas> containing @OneToMany objects. Following code instead of List<PrekeTiekejas> returns single PrekeTiekejas object. What I did wrong?

CriteriaBuilder cb = MinutisManager.getInstance().getCriteriaBuilder();
CriteriaQuery<Tuple> criteriaQuery = cb.createTupleQuery();
Root<minutis.Preke> from = criteriaQuery.from(minutis.Preke.class);
ListJoin<minutis.Preke, minutis.PrekeTiekejas> tiekejai = from.joinList(Preke_.tiekejai.getName());
criteriaQuery.multiselect(from, tiekejai);

TypedQuery<Tuple> typedQuery = MinutisManager.getInstance().createQuery(criteriaQuery);
//pages
typedQuery.setFirstResult(0);
typedQuery.setMaxResults(100);
//typedQuery.setHint(QueryHints.REFRESH, HintValues.TRUE);

List<Tuple> tuples = typedQuery.getResultList();
for(Tuple t : tuples) {
   minutis.Preke preke = t.get(0, minutis.Preke.class);
   System.out.println(preke.getPavadinimas());
   List<minutis.PrekeTiekejas> tiek = t.get(1, List.class);
   System.out.println(tiek.size());
}

Here is the error I get:

Testcase: testGetPrekesByFilters(database.dao.PrekeDAOTest):    Caused an ERROR
Element 1 type interface java.util.List is invalid for result "***PrekeTiekejas.toString()***".
java.lang.IllegalArgumentException: Element 1 type interface java.util.List is invalid  for result "***PrekeTiekejas.toString()***".
    at org.eclipse.persistence.internal.jpa.querydef.TupleImpl.get(TupleImpl.java:89)
    at database.dao.PrekeDAOTest.testGetPrekesByFilters(PrekeDAOTest.java:69)

PrekeDAOTest.java:69 line:

List<minutis.PrekeTiekejas> tiek = t.get(1, List.class);

Solution

  • JPA does not support returning collections as you are expecting, and instead returns a tuple for each Preke PrekeTiekejas pair. You will need to handle the results and build the List list yourself, or change what you are expecting.

    Since you are querying for Preke entities which already have a collection mapping to PrekeTiekejas you could just return Preke and call getPrekeTiekejas() on each entity for the collection. Use root.fetch(Preke_.tiekejai.getName(), JoinType.LEFT) instead of the joinList in the query to ensure the related entities are fetched in the query. Something like:

    CriteriaQuery<minutis.Preke> criteriaQuery = cb.createQuery(minutis.Preke.class);
    Root<minutis.Preke> from = criteriaQuery.from(minutis.Preke.class);
    from.fetch(Preke_.tiekejai.getName(), JoinType.LEFT);
    criteriaQuery.select(from);//not really needed
    
    TypedQuery<minutis.Preke> typedQuery = MinutisManager.getInstance().createQuery(criteriaQuery);
    //pages
    typedQuery.setFirstResult(0);
    typedQuery.setMaxResults(100);
    //typedQuery.setHint(QueryHints.REFRESH, HintValues.TRUE);
    
    List<minutis.Preke> prekeList= typedQuery.getResultList();
    for(minutis.Preke p : prekeList) {
       System.out.println(preke.getPavadinimas());
       List<minutis.PrekeTiekejas> tiek = preke.getTiekejai();//assumes tiekejai has a getTiekejai accessor
       System.out.println(tiek.size());
    }