Created
August 18, 2017 10:43
-
-
Save koraktor/52d5f1ffcc12768000e5a4e7b9fa0d1f to your computer and use it in GitHub Desktop.
Combining specifications and projections in Spring Data JPA
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 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
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)