There may be situations where we need to return a list of tuple rows from the associated data model i.e not a fully qualified entity but a part of it, specifically a list of selected columns from the associated data-source (may be a database).
I know of some of ways to return a list of tuple rows from the database using JPA like the following
There is no need to look closely into the code from the JPA criteria API, if you were to dislike criteria queries. The question is not directly related to JPA criteria. I prefer JPA criteria to JPQL for no precise reason - just because I like criteria queries very much.
Using a list of object arrays - List<Object[]>
:
public List<Object[]> object(int first, int pageSize) {
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]>criteriaQuery=criteriaBuilder.createQuery(Object[].class);
Root<Product> root = criteriaQuery.from(entityManager.getMetamodel().entity(Product.class));
List<Selection<?>>selections=new ArrayList<Selection<?>>();
selections.add(root.get(Product_.prodId));
selections.add(root.get(Product_.prodName));
selections.add(root.get(Product_.prodCode));
selections.add(root.get(Product_.prodDesc));
selections.add(root.get(Product_.marketPrice));
selections.add(root.get(Product_.salePrice));
criteriaQuery.select(criteriaBuilder.array(selections.toArray(new Selection[0])));
//Or criteriaQuery.multiselect(selections.toArray(new Selection[0]));
return entityManager.createQuery(criteriaQuery).setFirstResult(first).setMaxResults(pageSize).getResultList();
}
Using a list of tuples - List<Tuple>
:
public List<Tuple> tuple(int first, int pageSize) {
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple>criteriaQuery=criteriaBuilder.createTupleQuery();
Root<Product> root = criteriaQuery.from(entityManager.getMetamodel().entity(Product.class));
List<Selection<?>>selections=new ArrayList<Selection<?>>();
selections.add(root.get(Product_.prodId));
selections.add(root.get(Product_.prodName));
selections.add(root.get(Product_.prodCode));
selections.add(root.get(Product_.prodDesc));
selections.add(root.get(Product_.marketPrice));
selections.add(root.get(Product_.salePrice));
criteriaQuery.select(criteriaBuilder.tuple(selections.toArray(new Selection[0])));
//Or criteriaQuery.multiselect(selections.toArray(new Selection[0]));
return entityManager.createQuery(criteriaQuery).setFirstResult(first).setMaxResults(pageSize).getResultList();
}
Using a list of rows mapped a class of objects - List<MappedClass>
:
public List<ProductUtils> constructor(int first, int pageSize) {
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<ProductUtils>criteriaQuery=criteriaBuilder.createQuery(ProductUtils.class);
Root<Product> root = criteriaQuery.from(entityManager.getMetamodel().entity(Product.class));
List<Selection<?>>selections=new ArrayList<Selection<?>>();
selections.add(root.get(Product_.prodId));
selections.add(root.get(Product_.prodName));
selections.add(root.get(Product_.prodCode));
selections.add(root.get(Product_.prodDesc));
selections.add(root.get(Product_.marketPrice));
selections.add(root.get(Product_.salePrice));
criteriaQuery.select(criteriaBuilder.construct(ProductUtils.class, selections.toArray(new Selection[0])));
//Or criteriaQuery.multiselect(selections.toArray(new Selection[0]));
return entityManager.createQuery(criteriaQuery).setFirstResult(first).setMaxResults(pageSize).getResultList();
}
Again the same thing can be rewritten using JPQL.
The first two of them are ugly and require accessing properties using indices in EL on XHTML pages. Maintaining them is difficult, if the order in which the fields appear is changed at a later time (of course, aliases can be used with Tuple
). Also, use of Tuple
is always avoidable, since it requires an additional dependency in JSF from the javax.persistence
package increasing coupling between modules.
Using a constructor query to map the result list to a class may suffice. It can be used along with PrimeFaces LazyDataModel
as follows.
@Named
@ViewScoped
public class TestManagedBean extends LazyDataModel<ProductUtils> implements Serializable {
@Inject
private Service service;
private static final long serialVersionUID=1L;
public TestManagedBean() {}
@Override
public List<ProductUtils> load(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {
// Put some logic here like setting total rows for LazyDataModel - setRowCount(10)
return service.constructor(first, pageSize); //Use filters and sort meta whenever necessary.
}
}
But this is also too unmaintainable, if I need to access more or less fields from the database at some later time at a different place that requires creating a new class or adding a new constructor (constructor overloading in the existing class) to the existing class which in turn requires to check carefully the actual and formal parameters of the constructor method to see, if they match in number, order and type precisely that often makes me blind.
I hope, there should be some better ways that allow us to tackle such situations in a precise way.
Parameterized constructor(s) in the existing entity classes, if used instead (without creating a new class like ProductUtils
, in this case) may cause problems while implementing web services (JAX-WS) in the application (if needed). Therefore, I never tend to use parameterized constructors of entity classes anywhere.