DATAKV-121 - Added support for projections on repository query methods.
Repository methods can now return interfaces that act as projections on the original entity instances stored in the repository. Related ticket: DATACMNS-89.
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -13,7 +13,7 @@
|
|||||||
<groupId>org.springframework.data.build</groupId>
|
<groupId>org.springframework.data.build</groupId>
|
||||||
<artifactId>spring-data-parent</artifactId>
|
<artifactId>spring-data-parent</artifactId>
|
||||||
<version>1.8.0.BUILD-SNAPSHOT</version>
|
<version>1.8.0.BUILD-SNAPSHOT</version>
|
||||||
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
|
<relativePath />
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>joda-time</groupId>
|
<groupId>joda-time</groupId>
|
||||||
<artifactId>joda-time</artifactId>
|
<artifactId>joda-time</artifactId>
|
||||||
<version>2.5</version>
|
<version>${jodatime}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014 the original author or authors.
|
* Copyright 2014-2015 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -20,6 +20,7 @@ import java.lang.reflect.Constructor;
|
|||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.data.domain.PageImpl;
|
import org.springframework.data.domain.PageImpl;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.keyvalue.core.IterableConverter;
|
import org.springframework.data.keyvalue.core.IterableConverter;
|
||||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||||
@@ -28,10 +29,12 @@ import org.springframework.data.repository.query.ParameterAccessor;
|
|||||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
import org.springframework.data.repository.query.RepositoryQuery;
|
import org.springframework.data.repository.query.RepositoryQuery;
|
||||||
|
import org.springframework.data.repository.query.ResultProcessor;
|
||||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||||
import org.springframework.data.repository.query.parser.PartTree;
|
import org.springframework.data.repository.query.parser.PartTree;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.spel.standard.SpelExpression;
|
import org.springframework.expression.spel.standard.SpelExpression;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,25 +53,58 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
|
|||||||
|
|
||||||
private KeyValueQuery<?> query;
|
private KeyValueQuery<?> query;
|
||||||
|
|
||||||
public KeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evalContextProvider,
|
/**
|
||||||
|
* Creates a new {@link KeyValuePartTreeQuery} for the given {@link QueryMethod}, {@link EvaluationContextProvider},
|
||||||
|
* {@link KeyValueOperations} and query creator type.
|
||||||
|
*
|
||||||
|
* @param queryMethod must not be {@literal null}.
|
||||||
|
* @param evaluationContextProvider must not be {@literal null}.
|
||||||
|
* @param keyValueOperations must not be {@literal null}.
|
||||||
|
* @param queryCreator must not be {@literal null}.
|
||||||
|
*/
|
||||||
|
public KeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evaluationContextProvider,
|
||||||
KeyValueOperations keyValueOperations, Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
|
KeyValueOperations keyValueOperations, Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
|
||||||
|
|
||||||
|
Assert.notNull(queryMethod, "Query method must not be null!");
|
||||||
|
Assert.notNull(evaluationContextProvider, "EvaluationContextprovider must not be null!");
|
||||||
|
Assert.notNull(keyValueOperations, "KeyValueOperations must not be null!");
|
||||||
|
Assert.notNull(queryCreator, "QueryCreator type must not be null!");
|
||||||
|
|
||||||
this.queryMethod = queryMethod;
|
this.queryMethod = queryMethod;
|
||||||
this.keyValueOperations = keyValueOperations;
|
this.keyValueOperations = keyValueOperations;
|
||||||
this.evaluationContextProvider = evalContextProvider;
|
this.evaluationContextProvider = evaluationContextProvider;
|
||||||
this.queryCreator = queryCreator;
|
this.queryCreator = queryCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public QueryMethod getQueryMethod() {
|
public QueryMethod getQueryMethod() {
|
||||||
return queryMethod;
|
return queryMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object execute(Object[] parameters) {
|
public Object execute(Object[] parameters) {
|
||||||
|
|
||||||
KeyValueQuery<?> query = prepareQuery(parameters);
|
ParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(), parameters);
|
||||||
|
KeyValueQuery<?> query = prepareQuery(parameters, accessor);
|
||||||
|
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor);
|
||||||
|
|
||||||
|
return processor.processResult(doExecute(parameters, query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param parameters
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
protected Object doExecute(Object[] parameters, KeyValueQuery<?> query) {
|
||||||
|
|
||||||
if (queryMethod.isPageQuery() || queryMethod.isSliceQuery()) {
|
if (queryMethod.isPageQuery() || queryMethod.isSliceQuery()) {
|
||||||
|
|
||||||
@@ -78,8 +114,8 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
|
|||||||
|
|
||||||
Iterable<?> result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
|
Iterable<?> result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
|
||||||
|
|
||||||
long count = queryMethod.isSliceQuery() ? 0 : keyValueOperations.count(query, queryMethod.getEntityInformation()
|
long count = queryMethod.isSliceQuery() ? 0
|
||||||
.getJavaType());
|
: keyValueOperations.count(query, queryMethod.getEntityInformation().getJavaType());
|
||||||
|
|
||||||
return new PageImpl(IterableConverter.toList(result), page, count);
|
return new PageImpl(IterableConverter.toList(result), page, count);
|
||||||
|
|
||||||
@@ -91,16 +127,13 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
|
|||||||
|
|
||||||
Iterable<?> result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
|
Iterable<?> result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType());
|
||||||
return result.iterator().hasNext() ? result.iterator().next() : null;
|
return result.iterator().hasNext() ? result.iterator().next() : null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Query method not supported.");
|
throw new UnsupportedOperationException("Query method not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
private KeyValueQuery<?> prepareQuery(Object[] parameters) {
|
private KeyValueQuery<?> prepareQuery(Object[] parameters, ParameterAccessor accessor) {
|
||||||
|
|
||||||
ParametersParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(), parameters);
|
|
||||||
|
|
||||||
if (this.query == null) {
|
if (this.query == null) {
|
||||||
this.query = createQuery(accessor);
|
this.query = createQuery(accessor);
|
||||||
@@ -116,11 +149,9 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
|
|||||||
q.setRows(-1);
|
q.setRows(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessor.getSort() != null) {
|
Sort sort = accessor.getSort();
|
||||||
q.setSort(accessor.getSort());
|
|
||||||
} else {
|
q.setSort(sort != null ? sort : query.getSort());
|
||||||
q.setSort(this.query.getSort());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (q.getCritieria() instanceof SpelExpression) {
|
if (q.getCritieria() instanceof SpelExpression) {
|
||||||
EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(),
|
EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(),
|
||||||
@@ -131,7 +162,7 @@ public class KeyValuePartTreeQuery implements RepositoryQuery {
|
|||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValueQuery<?> createQuery(ParametersParameterAccessor accessor) {
|
public KeyValueQuery<?> createQuery(ParameterAccessor accessor) {
|
||||||
|
|
||||||
PartTree tree = new PartTree(getQueryMethod().getName(), getQueryMethod().getEntityInformation().getJavaType());
|
PartTree tree = new PartTree(getQueryMethod().getName(), getQueryMethod().getEntityInformation().getJavaType());
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
|
|||||||
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
|
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
|
||||||
import org.springframework.data.mapping.PersistentEntity;
|
import org.springframework.data.mapping.PersistentEntity;
|
||||||
import org.springframework.data.mapping.context.MappingContext;
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
|
import org.springframework.data.projection.ProjectionFactory;
|
||||||
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
|
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
|
||||||
import org.springframework.data.repository.core.EntityInformation;
|
import org.springframework.data.repository.core.EntityInformation;
|
||||||
import org.springframework.data.repository.core.NamedQueries;
|
import org.springframework.data.repository.core.NamedQueries;
|
||||||
@@ -170,13 +171,15 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport {
|
|||||||
this.queryCreator = queryCreator;
|
this.queryCreator = queryCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries)
|
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries)
|
||||||
*/
|
*/
|
||||||
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) {
|
@Override
|
||||||
|
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||||
|
NamedQueries namedQueries) {
|
||||||
|
|
||||||
QueryMethod queryMethod = new QueryMethod(method, metadata);
|
QueryMethod queryMethod = new QueryMethod(method, metadata, factory);
|
||||||
return new KeyValuePartTreeQuery(queryMethod, evaluationContextProvider, this.keyValueOperations,
|
return new KeyValuePartTreeQuery(queryMethod, evaluationContextProvider, this.keyValueOperations,
|
||||||
this.queryCreator);
|
this.queryCreator);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import org.mockito.runners.MockitoJUnitRunner;
|
|||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||||
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
|
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
|
||||||
|
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
@@ -100,11 +101,12 @@ public class SpelQueryEngineUnitTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Method method = PersonRepository.class.getMethod(methodName, types.toArray(new Class<?>[types.size()]));
|
Method method = PersonRepository.class.getMethod(methodName, types.toArray(new Class<?>[types.size()]));
|
||||||
|
|
||||||
RepositoryMetadata metadata = mock(RepositoryMetadata.class);
|
RepositoryMetadata metadata = mock(RepositoryMetadata.class);
|
||||||
|
doReturn(method.getReturnType()).when(metadata).getReturnedDomainClass(method);
|
||||||
|
|
||||||
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
|
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
|
||||||
SpelQueryCreator creator = new SpelQueryCreator(partTree,
|
SpelQueryCreator creator = new SpelQueryCreator(partTree, new ParametersParameterAccessor(
|
||||||
new ParametersParameterAccessor(new QueryMethod(method, metadata).getParameters(), args));
|
new QueryMethod(method, metadata, new SpelAwareProxyProjectionFactory()).getParameters(), args));
|
||||||
|
|
||||||
KeyValueQuery<SpelExpression> query = creator.createQuery();
|
KeyValueQuery<SpelExpression> query = creator.createQuery();
|
||||||
query.getCritieria().setEvaluationContext(new StandardEvaluationContext(args));
|
query.getCritieria().setEvaluationContext(new StandardEvaluationContext(args));
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package org.springframework.data.keyvalue.repository.query;
|
|||||||
|
|
||||||
import static org.hamcrest.core.Is.*;
|
import static org.hamcrest.core.Is.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -30,6 +31,7 @@ import org.mockito.runners.MockitoJUnitRunner;
|
|||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||||
|
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
@@ -238,7 +240,7 @@ public class SpelQueryCreatorUnitTests {
|
|||||||
* @see DATACMNS-525
|
* @see DATACMNS-525
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void greaterThanEaualsReturnsTrueForEqualValues() throws Exception {
|
public void greaterThanEqualsReturnsTrueForEqualValues() throws Exception {
|
||||||
assertThat(evaluate("findByAgeGreaterThanEqual", BRAN.age).against(BRAN), is(true));
|
assertThat(evaluate("findByAgeGreaterThanEqual", BRAN.age).against(BRAN), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,10 +396,11 @@ public class SpelQueryCreatorUnitTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Method method = PersonRepository.class.getMethod(methodName, argTypes);
|
Method method = PersonRepository.class.getMethod(methodName, argTypes);
|
||||||
|
doReturn(Person.class).when(metadataMock).getReturnedDomainClass(method);
|
||||||
|
|
||||||
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
|
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
|
||||||
SpelQueryCreator creator = new SpelQueryCreator(partTree, new ParametersParameterAccessor(new QueryMethod(method,
|
SpelQueryCreator creator = new SpelQueryCreator(partTree, new ParametersParameterAccessor(
|
||||||
metadataMock).getParameters(), args));
|
new QueryMethod(method, metadataMock, new SpelAwareProxyProjectionFactory()).getParameters(), args));
|
||||||
|
|
||||||
KeyValueQuery<SpelExpression> q = creator.createQuery();
|
KeyValueQuery<SpelExpression> q = creator.createQuery();
|
||||||
q.getCritieria().setEvaluationContext(new StandardEvaluationContext(args));
|
q.getCritieria().setEvaluationContext(new StandardEvaluationContext(args));
|
||||||
|
|||||||
@@ -155,6 +155,34 @@ public abstract class AbstractRepositoryUnitTests<T extends AbstractRepositoryUn
|
|||||||
assertThat(result, contains(TYRION, JAIME, CERSEI));
|
assertThat(result, contains(TYRION, JAIME, CERSEI));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAKV-121
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void projectsResultToInterface() {
|
||||||
|
|
||||||
|
repository.save(LENNISTERS);
|
||||||
|
|
||||||
|
List<PersonSummary> result = repository.findByAgeGreaterThan(0, new Sort("firstname"));
|
||||||
|
|
||||||
|
assertThat(result, hasSize(3));
|
||||||
|
assertThat(result.get(0).getFirstname(), is(CERSEI.getFirstname()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAKV-121
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void projectsResultToDynamicInterface() {
|
||||||
|
|
||||||
|
repository.save(LENNISTERS);
|
||||||
|
|
||||||
|
List<PersonSummary> result = repository.findByAgeGreaterThan(0, new Sort("firstname"), PersonSummary.class);
|
||||||
|
|
||||||
|
assertThat(result, hasSize(3));
|
||||||
|
assertThat(result.get(0).getFirstname(), is(CERSEI.getFirstname()));
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract T getRepository(KeyValueRepositoryFactory factory);
|
protected abstract T getRepository(KeyValueRepositoryFactory factory);
|
||||||
|
|
||||||
public static interface PersonRepository extends CrudRepository<Person, String>, KeyValueRepository<Person, String> {
|
public static interface PersonRepository extends CrudRepository<Person, String>, KeyValueRepository<Person, String> {
|
||||||
@@ -173,6 +201,13 @@ public abstract class AbstractRepositoryUnitTests<T extends AbstractRepositoryUn
|
|||||||
|
|
||||||
List<Person> findByAgeGreaterThanOrderByAgeAscFirstnameDesc(int age);
|
List<Person> findByAgeGreaterThanOrderByAgeAscFirstnameDesc(int age);
|
||||||
|
|
||||||
|
List<PersonSummary> findByAgeGreaterThan(int age, Sort sort);
|
||||||
|
|
||||||
|
<T> List<T> findByAgeGreaterThan(int age, Sort sort, Class<T> projectionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PersonSummary {
|
||||||
|
|
||||||
|
String getFirstname();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user