to add support for paging and selective dynamic eager fetching of attributes. here are the interface methods that could be added to the EntityDao
/**
* find by a typed query. The given query must define exactly one root.
*
* @param query the typed query to use
* @param paging page def
* @param fetch fetch def
* @param <E> entity type
* @return result of executing given query according to specified limit
*/
<E> SearchResultList<E> findByQuery(final CriteriaQuery<E> query, @Nullable final YPage paging,
@Nullable final YFetch fetch);
/**
* Returns all instances of the type according to the specified paging
*
* @return all entities in accordance with the specified paging definition
*/
SearchResultList<E> find(final @Nullable YPage paging, final @Nullable YFetch fetch);
the idea that paging/fetching is encapsulated in an instance of YPage or YFetch respectively which is passed to the query method.
below are the implementations. note that the class parameter is deduced from the generics information e.g., PersonDao extends EntityDao<Person, Long>, imply that the class is Person.
public <T> SearchResultList<T> find(final Class<T> type, final @Nullable YPage paging,
final @Nullable YFetch fetch) {
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<T> cq = builder.createQuery(type);
Root<T> root = cq.from(type);
cq.select(root);
return executeQuery(cq, paging, fetch, builder, root);
}
/**
* execute query with given properties
*
* @param select root query
* @param paging paging
* @param criteriaBuilder root builder
* @param from parent from
* @return executed list
*/
private <T> SearchResultList<T> executeQuery(CriteriaQuery<T> select, YPage paging, YFetch fetch,
CriteriaBuilder criteriaBuilder, Root<T> from) {
if (paging == null || !paging.isPaged()) {
TypedQuery<T> typedQuery = getEntityManager().createQuery(select);
applyFetch(fetch, typedQuery);
return new SearchResultList<T>(typedQuery.getResultList());
}
//do the total count before setting the limits so as to work out the total retrievable.
//note that setting the limit may affect the restrictions e.g., by appending the equivalent of
//'where from.id >= paging.getFirstResultIndex'
long totalCount = count(from, select.getRestriction(), criteriaBuilder);
setOrders(select, paging, criteriaBuilder, from, new HashMap<String, From>());
TypedQuery<T> typedQuery = getEntityManager().createQuery(select);
setLimits(typedQuery, paging);
applyFetch(fetch, typedQuery);
return new SearchResultList<T>(typedQuery.getResultList(), totalCount, paging);
}
/**
* set Orders of given criteria.
*
* @param select query select
* @param paging query page definition
* @param criteriaBuilder criteria builder
* @param from root from
* @param joins joins map
*/
private void setOrders(CriteriaQuery select, YPage paging, CriteriaBuilder criteriaBuilder, Root from,
Map<String, From> joins) {
if (paging == null) {
return;
}
if (paging.isOrdered()) {
ArrayList<Order> jpaOrders = new ArrayList<Order>();
List<YOrder> orders = paging.getOrders();
//to set the jpa orders, we first need to join on the objects whose properties we are using to order by
//e.g., if we have 'order by employers.name', we will first need to join to employers if not already joined
for (YOrder daoOrder : orders) {
Path path = extractPath(from, joins, daoOrder.getName());
Order jpaOrder = (daoOrder.getSort().equals(YOrder.Sort.ASC)) ? criteriaBuilder.asc(path)
: criteriaBuilder.desc(path);
jpaOrders.add(jpaOrder);
}
select.orderBy(jpaOrders.toArray(new Order[0]));
}
}
private Path extractPath(Root from, Map<String, From> joins, String property) {
Path path;
String[] strings = DaoUtils.splitDot(property);
From join = getLatestJoin(from, joins, strings);
path = join.get(strings[strings.length - 1]);
return path;
}
/**
* create latest join from given root criteria from
*
* @param from root
* @param joins join map cache
* @param strings '.' splitted field array
* @return get latest join before property
*/
private From getLatestJoin(Root from, Map<String, From> joins, String[] strings) {
// /not implemented yet
From join = from;
for (int j = 0; j < strings.length - 1; j++) {
String propertyName = strings[j];
String key = keyMaker(strings, j);
if (joins.containsKey(key)) {
join = joins.get(key);
} else {
if (j == 0) {
join = from.join(propertyName);
} else {
join = join.join(propertyName);
}
joins.put(key, join);
}
}
return join;
}
/**
* given a property path, construct a (sub)path ending at index j. Uses '.' as the path separator
*
* @param strings property path
* @param j index of last property
* @return constructed (sub)path
*/
private String keyMaker(String[] strings, int j) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i <= j; i++) {
String string = strings[i];
if (i > 0) {
stringBuilder.append(".");
}
stringBuilder.append(string);
}
return stringBuilder.toString();
}
/**
* set limits for query
*
* @param query query
* @param limit page limits
*/
private void setLimits(Query query, YLimit limit) {
if (limit == null) {
return;
}
if (limit.isPaged()) {
query.setFirstResult(limit.getFirstResultIndex());
}
if (limit.isPageSized()) {
query.setMaxResults(limit.getPageSize());
}
}
private void applyFetch(YFetch fetch, Query query){
if(fetch != null){
persistenceJPAFetchStrategy.applyFetch(fetch, query);
}
}
public interface IPersistenceJPAFetchStrategy {
public void applyFetch(YFetch fetch, Query query);
}
@Alternative
public class OpenJpaFetchStrategy implements IPersistenceJPAFetchStrategy {
public void applyFetch(YFetch fetch, Query query) {
if (fetch != null) {
OpenJPAQuery openJPAQuery = OpenJPAPersistence.cast(query);
FetchPlan fetchPlan = openJPAQuery.getFetchPlan();
fetchPlan.setMaxFetchDepth(fetch.getMaxFetchDepth());
Map<Class, Collection<String>> fetchProps = fetch.getFetchProperties();
for (Class clazz : fetchProps.keySet()) {
Collection<String> fieldNames = fetchProps.get(clazz);
fetchPlan.addFields(clazz, fieldNames);
}
}
}
}
@Alternative
public class EclipseLinkFetchStrategy implements IPersistenceJPAFetchStrategy {
@OverRide
public void applyFetch(YFetch fetch, Query query) {
throw new UnsupportedOperationException("Fetch strategy is not currently supported for eclipse link");
}
}
to add support for paging and selective dynamic eager fetching of attributes. here are the interface methods that could be added to the EntityDao
the idea that paging/fetching is encapsulated in an instance of YPage or YFetch respectively which is passed to the query method.
below are the implementations. note that the class parameter is deduced from the generics information e.g., PersonDao extends EntityDao<Person, Long>, imply that the class is Person.
public interface IPersistenceJPAFetchStrategy {
public void applyFetch(YFetch fetch, Query query);
}
@Alternative
public class OpenJpaFetchStrategy implements IPersistenceJPAFetchStrategy {
}
@Alternative
public class EclipseLinkFetchStrategy implements IPersistenceJPAFetchStrategy {
@OverRide
public void applyFetch(YFetch fetch, Query query) {
throw new UnsupportedOperationException("Fetch strategy is not currently supported for eclipse link");
}
}