Skip to content

Instantly share code, notes, and snippets.

@koraktor
Created August 18, 2017 10:43
Show Gist options
  • Save koraktor/52d5f1ffcc12768000e5a4e7b9fa0d1f to your computer and use it in GitHub Desktop.
Save koraktor/52d5f1ffcc12768000e5a4e7b9fa0d1f to your computer and use it in GitHub Desktop.
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