Skip to content

Instantly share code, notes, and snippets.

@koraktor
Created August 18, 2017 10:43
Combining specifications and projections in Spring Data JPA
public class RepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID extends Serializable> {
ProjectionFactory projectionFactory;
public <P> List<P> findProjected(Specification<?> spec, Sort sort, Class<P> projectionClass) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleQuery = criteriaBuilder.createTupleQuery();
Root<?> root = tupleQuery.from(getDomainClass());
// Gathering selections from the projection class
// `toExpressionRecursively` would be imported from QueryUtils
Set<Selection<?>> selections = new HashSet<>();
List<PropertyDescriptor> inputProperties = projectionFactory.getProjectionInformation(projectionClass).getInputProperties();
for (PropertyDescriptor propertyDescriptor : inputProperties) {
String property = propertyDescriptor.getName();
PropertyPath path = PropertyPath.from(property, getDomainClass());
selections.add(toExpressionRecursively(root, path).alias(property));
}
// Select, restrict and order
tupleQuery.multiselect(new ArrayList<>(selections))
.where(spec.toPredicate((Root) root, tupleQuery, criteriaBuilder))
.orderBy(QueryUtils.toOrders(sort, root, criteriaBuilder));
TypedQuery<Tuple> query = entityManager.createQuery(tupleQuery);
List<Tuple> results = query.getResultList();
// Create maps for each result tuple
List<P> projectedResults = new ArrayList<>(results.size());
for (Tuple tuple : results) {
Map<String, Object> mappedResult = new HashMap<>(tuple.getElements().size());
for (TupleElement<?> element : tuple.getElements()) {
String name = element.getAlias();
mappedResult.put(name, tuple.get(name));
}
projectedResults.add(projectionFactory.createProjection(projectionClass, mappedResult));
}
return mappedResults;
}
}
@dromerop
Copy link

what would be the best workarround to avoid the "package-private" scope of QueryUtils.toExpressionRecursively ??? (see https://github.com/spring-projects/spring-data-jpa/blob/1.11.x/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java)

@koraktor
Copy link
Author

@dromerop Currently, you may want to rely on some reflection magic or copy and paste the code into your repository implementation. In the latter case you would need to check for differences in the original code after each update of Spring Data JPA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment