DATACOUCH-246 - Projections support for query derivation.
Used the result processor projecting information to just fetch the selected fields and again to post process results to projection object/interfaces. Added test for projection using DTO. Original pull request: #122.
This commit is contained in:
committed by
Oliver Gierke
parent
c8324caf7f
commit
ce356f827f
@@ -95,7 +95,7 @@ public class CouchbaseTemplateTests {
|
||||
String id = "beers:awesome-stout";
|
||||
String name = "The Awesome Stout";
|
||||
boolean active = false;
|
||||
Beer beer = new Beer(id).setName(name).setActive(active);
|
||||
Beer beer = new Beer(id, name, active, "");
|
||||
|
||||
template.save(beer);
|
||||
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
|
||||
@@ -163,7 +163,7 @@ public class CouchbaseTemplateTests {
|
||||
@Test
|
||||
public void removeDocument() {
|
||||
String id = "beers:to-delete-stout";
|
||||
Beer beer = new Beer(id);
|
||||
Beer beer = new Beer(id, "", false, "");
|
||||
|
||||
template.save(beer);
|
||||
Object result = client.get(id);
|
||||
@@ -208,7 +208,7 @@ public class CouchbaseTemplateTests {
|
||||
String id = "beers:findme-stout";
|
||||
String name = "The Findme Stout";
|
||||
boolean active = true;
|
||||
Beer beer = new Beer(id).setName(name).setActive(active);
|
||||
Beer beer = new Beer(id, name, active, "");
|
||||
template.save(beer);
|
||||
|
||||
Beer found = template.findById(id, Beer.class);
|
||||
|
||||
@@ -45,7 +45,7 @@ public class CouchbaseTemplateViewListener extends DependencyInjectionTestExecut
|
||||
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Beer b = new Beer("testbeer-" + i).setName("MyBeer " + i).setActive(true);
|
||||
Beer b = new Beer("testbeer-" + i, "MyBeer" + i, true, "");
|
||||
template.save(b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class TypeKeyTests {
|
||||
String id = "beers:awesome-stout";
|
||||
String name = "The Awesome Stout";
|
||||
boolean active = false;
|
||||
Beer beer = new Beer(id).setName(name).setActive(active);
|
||||
Beer beer = new Beer(id, name, active, "");
|
||||
|
||||
template.save(beer);
|
||||
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
|
||||
|
||||
@@ -656,4 +656,91 @@ By storing 3 items using a `MyRepository` instance and calling `count()` then `c
|
||||
----
|
||||
100
|
||||
-3
|
||||
----
|
||||
----
|
||||
|
||||
=== DTO Projections
|
||||
Spring Data Repositories usually return the domain model when using query methods. However, sometimes, you may need to alter the view of that model for various reasons. In this section, you will learn how to define projections to serve up simplified and reduced views of resources.
|
||||
|
||||
Look at the following domain model:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
private String firstName, lastName;
|
||||
|
||||
@OneToOne
|
||||
private Address address;
|
||||
…
|
||||
}
|
||||
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
private String street, state, country;
|
||||
|
||||
…
|
||||
}
|
||||
----
|
||||
|
||||
This `Person` has several attributes:
|
||||
|
||||
* `id` is the primary key
|
||||
* `firstName` and `lastName` are data attributes
|
||||
* `address` is a link to another domain object
|
||||
|
||||
Now assume we create a corresponding repository as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
Person findPersonByFirstName(String firstName);
|
||||
}
|
||||
----
|
||||
|
||||
Spring Data will return the domain object including all of its attributes. There are two options just to retrieve the `address` attribute. One option is to define a repository for `Address` objects like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface AddressRepository extends CrudRepository<Address, Long> {}
|
||||
----
|
||||
|
||||
In this situation, using `PersonRepository` will still return the whole `Person` object. Using `AddressRepository` will return just the `Address`.
|
||||
|
||||
However, what if you do not want to expose `address` details at all? You can offer the consumer of your repository service an alternative by defining one or more projections.
|
||||
|
||||
.Simple Projection
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface NoAddresses { <1>
|
||||
|
||||
String getFirstName(); <2>
|
||||
|
||||
String getLastName(); <3>
|
||||
}
|
||||
----
|
||||
This projection has the following details:
|
||||
|
||||
<1> A plain Java interface making it declarative.
|
||||
<2> Export the `firstName`.
|
||||
<3> Export the `lastName`.
|
||||
====
|
||||
|
||||
The `NoAddresses` projection only has getters for `firstName` and `lastName` meaning that it will not serve up any address information. The query method definition returns in this case `NoAdresses` instead of `Person`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
NoAddresses findByFirstName(String firstName);
|
||||
}
|
||||
----
|
||||
|
||||
Projections declare a contract between the underlying type and the method signatures related to the exposed properties. Hence it is required to name getter methods according to the property name of the underlying type. If the underlying property is named `firstName`, then the getter method must be named `getFirstName` otherwise Spring Data is not able to look up the source property.
|
||||
@@ -20,7 +20,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.couchbase.client.core.lang.Tuple2;
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.document.json.JsonValue;
|
||||
@@ -40,12 +39,17 @@ import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base for all Couchbase {@link RepositoryQuery}. It is in charge of inspecting the parameters
|
||||
* and choosing the correct {@link N1qlQuery} implementation to use.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
@@ -53,6 +57,7 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
protected final CouchbaseQueryMethod queryMethod;
|
||||
private final CouchbaseOperations couchbaseOperations;
|
||||
private Class<?> returnedType;
|
||||
|
||||
protected AbstractN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) {
|
||||
this.queryMethod = queryMethod;
|
||||
@@ -61,6 +66,7 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
/**
|
||||
* The statement for a count() query. This must aggregate using count with the alias {@link CountFragment#COUNT_ALIAS}.
|
||||
*
|
||||
* @see CountFragment
|
||||
*/
|
||||
protected abstract Statement getCount(ParameterAccessor accessor, Object[] runtimeParameters);
|
||||
@@ -71,14 +77,23 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
*/
|
||||
protected abstract boolean useGeneratedCountQuery();
|
||||
|
||||
protected abstract Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters);
|
||||
protected abstract Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType);
|
||||
|
||||
protected abstract JsonValue getPlaceholderValues(ParameterAccessor accessor);
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
||||
Statement statement = getStatement(accessor, parameters);
|
||||
|
||||
ResultProcessor processor = this.queryMethod.getResultProcessor().withDynamicProjection(accessor);
|
||||
ReturnedType returnedType = processor.getReturnedType();
|
||||
this.returnedType = returnedType.getReturnedType();
|
||||
|
||||
if (this.returnedType.isInterface()) {
|
||||
this.returnedType = returnedType.getDomainType();
|
||||
}
|
||||
|
||||
Statement statement = getStatement(accessor, parameters, returnedType);
|
||||
JsonValue queryPlaceholderValues = getPlaceholderValues(accessor);
|
||||
|
||||
//prepare the final query
|
||||
@@ -89,9 +104,8 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
Statement countStatement = getCount(accessor, parameters);
|
||||
N1qlQuery countQuery = buildQuery(countStatement, queryPlaceholderValues,
|
||||
getCouchbaseOperations().getDefaultConsistency().n1qlConsistency());
|
||||
|
||||
return executeDependingOnType(query, countQuery, queryMethod, accessor.getPageable(),
|
||||
queryMethod.isPageQuery(), queryMethod.isSliceQuery(), queryMethod.isModifyingQuery());
|
||||
return processor.processResult(executeDependingOnType(query, countQuery, queryMethod, accessor.getPageable(),
|
||||
queryMethod.isPageQuery(), queryMethod.isSliceQuery(), queryMethod.isModifyingQuery()));
|
||||
}
|
||||
|
||||
protected static N1qlQuery buildQuery(Statement statement, JsonValue queryPlaceholderValues, ScanConsistency scanConsistency) {
|
||||
@@ -144,7 +158,7 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
|
||||
protected List<?> executeCollection(N1qlQuery query) {
|
||||
logIfNecessary(query);
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, queryMethod.getEntityInformation().getJavaType());
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, this.returnedType);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -169,14 +183,14 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
|
||||
}
|
||||
|
||||
logIfNecessary(query);
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, queryMethod.getEntityInformation().getJavaType());
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, this.returnedType);
|
||||
return new PageImpl(result, pageable, total);
|
||||
}
|
||||
|
||||
protected Object executeSliced(N1qlQuery query, N1qlQuery countQuery, Pageable pageable) {
|
||||
Assert.notNull(pageable);
|
||||
logIfNecessary(query);
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, queryMethod.getEntityInformation().getJavaType());
|
||||
List<?> result = couchbaseOperations.findByN1QL(query, this.returnedType);
|
||||
int pageSize = pageable.getPageSize();
|
||||
boolean hasNext = result.size() > pageSize;
|
||||
|
||||
|
||||
@@ -19,12 +19,7 @@ package org.springframework.data.couchbase.repository.query;
|
||||
import static com.couchbase.client.java.query.Select.select;
|
||||
import static com.couchbase.client.java.query.dsl.Expression.i;
|
||||
import static com.couchbase.client.java.query.dsl.functions.AggregateFunctions.count;
|
||||
import static com.couchbase.client.java.query.dsl.functions.MetaFunctions.meta;
|
||||
|
||||
import com.couchbase.client.core.lang.Tuple;
|
||||
import com.couchbase.client.core.lang.Tuple2;
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.document.json.JsonValue;
|
||||
import com.couchbase.client.java.query.Statement;
|
||||
import com.couchbase.client.java.query.dsl.Expression;
|
||||
@@ -36,9 +31,18 @@ import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link RepositoryQuery} for Couchbase, based on query derivation
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
|
||||
public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
|
||||
private final PartTree partTree;
|
||||
@@ -63,16 +67,15 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
return queryCreator.createQuery();
|
||||
}
|
||||
@Override
|
||||
protected Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters) {
|
||||
protected Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) {
|
||||
String bucketName = getCouchbaseOperations().getCouchbaseBucket().name();
|
||||
Expression bucket = N1qlUtils.escapedBucket(bucketName);
|
||||
|
||||
|
||||
FromPath select;
|
||||
if (partTree.isCountProjection()) {
|
||||
select = select(count("*"));
|
||||
} else {
|
||||
select = N1qlUtils.createSelectClauseForEntity(bucketName);
|
||||
select = N1qlUtils.createSelectClauseForEntity(bucketName, returnedType, this.getCouchbaseOperations().getConverter());
|
||||
}
|
||||
WherePath selectFrom = select.from(bucket);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
@@ -53,6 +54,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
* along with a variable for {@link #SPEL_FILTER WHERE clause filtering} of the correct entity.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
|
||||
@@ -264,7 +266,7 @@ public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters) {
|
||||
public Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) {
|
||||
String parsedStatement = parseSpel(this.originalStatement, false, runtimeParameters);
|
||||
return N1qlQuery.simple(parsedStatement).statement();
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ import com.couchbase.client.java.query.dsl.functions.TypeFunctions;
|
||||
import com.couchbase.client.java.query.dsl.path.FromPath;
|
||||
import com.couchbase.client.java.query.dsl.path.WherePath;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.couchbase.repository.query.CountFragment;
|
||||
@@ -44,10 +44,14 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
|
||||
/**
|
||||
* Utility class to deal with constructing well formed N1QL queries around Spring Data entities, so that
|
||||
* the framework can use N1QL to find such entities (eg. restrict the bucket search to a particular type).
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class N1qlUtils {
|
||||
|
||||
@@ -70,6 +74,41 @@ public class N1qlUtils {
|
||||
return i(bucketName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a {@link Statement} that corresponds to the SELECT clause for looking for Spring Data entities
|
||||
* stored in Couchbase. Notably it will select the content of the document AND its id and cas and use custom
|
||||
* construction of query if required.
|
||||
*
|
||||
* @param bucketName the bucket that stores the entity documents (will be escaped).
|
||||
* @param returnedType Returned type projection information from result processor.
|
||||
* @param converter couchbase converter
|
||||
* @return the needed SELECT clause of the statement.
|
||||
*/
|
||||
public static FromPath createSelectClauseForEntity(String bucketName, ReturnedType returnedType, CouchbaseConverter converter) {
|
||||
Expression bucket = escapedBucket(bucketName);
|
||||
Expression metaId = path(meta(bucket), "id").as(CouchbaseOperations.SELECT_ID);
|
||||
Expression metaCas = path(meta(bucket), "cas").as(CouchbaseOperations.SELECT_CAS);
|
||||
List<Expression> expList = new ArrayList<Expression>();
|
||||
expList.add(metaId);
|
||||
expList.add(metaCas);
|
||||
|
||||
if (returnedType != null && returnedType.needsCustomConstruction()) {
|
||||
List<String> properties = returnedType.getInputProperties();
|
||||
CouchbasePersistentEntity<?> entity = converter.getMappingContext().getPersistentEntity(returnedType.getDomainType());
|
||||
|
||||
for (String property : properties) {
|
||||
expList.add(path(bucket, i(entity.getPersistentProperty(property).getFieldName())));
|
||||
}
|
||||
} else {
|
||||
expList.add(path(bucket, "*"));
|
||||
}
|
||||
|
||||
Expression[] propertiesExp = new Expression[expList.size()];
|
||||
propertiesExp = expList.toArray(propertiesExp);
|
||||
|
||||
return select(propertiesExp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a {@link Statement} that corresponds to the SELECT clause for looking for Spring Data entities
|
||||
* stored in Couchbase. Notably it will select the content of the document AND its id and cas.
|
||||
@@ -78,11 +117,7 @@ public class N1qlUtils {
|
||||
* @return the needed SELECT clause of the statement.
|
||||
*/
|
||||
public static FromPath createSelectClauseForEntity(String bucketName) {
|
||||
Expression bucket = escapedBucket(bucketName);
|
||||
Expression metaId = path(meta(bucket), "id").as(CouchbaseOperations.SELECT_ID);
|
||||
Expression metaCas = path(meta(bucket), "cas").as(CouchbaseOperations.SELECT_CAS);
|
||||
|
||||
return select(metaId, metaCas, path(bucket, "*"));
|
||||
return createSelectClauseForEntity(bucketName, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.couchbase.client.java.repository.annotation.Field;
|
||||
* Test class for persisting and loading from {@link CouchbaseTemplate}.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class Beer {
|
||||
|
||||
@@ -35,13 +36,19 @@ public class Beer {
|
||||
@Field("is_active")
|
||||
private boolean active = true;
|
||||
|
||||
public Beer(String id) {
|
||||
@Field("desc")
|
||||
private String description;
|
||||
|
||||
public Beer(String id, String name, Boolean active, String description) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.active = active;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Beer [id=" + id + ", name=" + name + ", active=" + active + "]";
|
||||
return "Beer [id=" + id + ", name=" + name + ", active=" + active + ", description=" + description + "]";
|
||||
}
|
||||
|
||||
public Beer setName(String name) {
|
||||
@@ -62,6 +69,15 @@ public class Beer {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Beer setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
/**
|
||||
* Test DTO for projecting from {@link CouchbaseTemplate}.
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class BeerDTO{
|
||||
private String name;
|
||||
|
||||
@Field("desc")
|
||||
private String description;
|
||||
|
||||
public BeerDTO(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public BeerDTO setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
|
||||
public BeerDTO setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDescription() { return description; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
/**
|
||||
* Test interface for projecting data from {@link CouchbaseTemplate}.
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public interface BeerProjection {
|
||||
String getDescription();
|
||||
}
|
||||
@@ -6,10 +6,16 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.couchbase.core.BeerDTO;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
import org.springframework.data.mapping.*;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.couchbase.core.Beer;
|
||||
import org.springframework.data.couchbase.core.BeerProjection;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
@@ -18,10 +24,10 @@ import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -30,6 +36,7 @@ import org.springframework.data.repository.query.ParameterAccessor;
|
||||
|
||||
import com.couchbase.client.java.CouchbaseBucket;
|
||||
import com.couchbase.client.java.query.Statement;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
|
||||
public class PartTreeN1qBasedQueryTest {
|
||||
|
||||
@@ -117,12 +124,79 @@ public class PartTreeN1qBasedQueryTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProjectionInterface() throws Exception {
|
||||
|
||||
CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class);
|
||||
CouchbaseBucket couchbaseBucket = mock(CouchbaseBucket.class);
|
||||
CouchbaseConverter couchbaseConverter = mock(CouchbaseConverter.class);
|
||||
EntityMetadata entityInformation = mock(EntityMetadata.class);
|
||||
ParameterAccessor accessor = mock(ParameterAccessor.class);
|
||||
|
||||
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
factory.createProjection(BeerProjection.class);
|
||||
MappingContext mappingContext = new CouchbaseMappingContext();
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(TestRepository.class);
|
||||
Method method = TestRepository.class.getMethod("findAllProjectedBy");
|
||||
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext);
|
||||
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor);
|
||||
|
||||
|
||||
when(entityInformation.getJavaType()).thenReturn(Beer.class);
|
||||
when(couchbaseOperations.getCouchbaseBucket()).thenReturn(couchbaseBucket);
|
||||
when(couchbaseBucket.name()).thenReturn("B");
|
||||
when(couchbaseOperations.getConverter()).thenReturn(couchbaseConverter);
|
||||
when(couchbaseOperations.getConverter().getMappingContext()).thenReturn(mappingContext);
|
||||
when(couchbaseConverter.getTypeKey()).thenReturn("_class");
|
||||
when(couchbaseConverter.convertForWriteIfNeeded(eq("value"))).thenReturn("value");
|
||||
|
||||
PartTreeN1qlBasedQuery query = new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations);
|
||||
Statement statement = query.getStatement(accessor, null, processor.getReturnedType());
|
||||
|
||||
assertEquals("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.`desc` FROM `B` WHERE "
|
||||
+ "`_class` = \"org.springframework.data.couchbase.core.Beer\"", statement.toString());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProjectionDTO() throws Exception {
|
||||
CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class);
|
||||
CouchbaseBucket couchbaseBucket = mock(CouchbaseBucket.class);
|
||||
CouchbaseConverter couchbaseConverter = mock(CouchbaseConverter.class);
|
||||
EntityMetadata entityInformation = mock(EntityMetadata.class);
|
||||
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
ParameterAccessor accessor = mock(ParameterAccessor.class);
|
||||
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(TestRepository.class);
|
||||
Method method = TestRepository.class.getMethod("findAllDtoedBy");
|
||||
MappingContext mappingContext = new CouchbaseMappingContext();
|
||||
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext);
|
||||
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor);
|
||||
|
||||
when(entityInformation.getJavaType()).thenReturn(Beer.class);
|
||||
when(couchbaseOperations.getCouchbaseBucket()).thenReturn(couchbaseBucket);
|
||||
when(couchbaseBucket.name()).thenReturn("B");
|
||||
when(couchbaseOperations.getConverter()).thenReturn(couchbaseConverter);
|
||||
when(couchbaseOperations.getConverter().getMappingContext()).thenReturn(mappingContext);
|
||||
when(couchbaseConverter.getTypeKey()).thenReturn("_class");
|
||||
|
||||
PartTreeN1qlBasedQuery query = new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations);
|
||||
Statement statement = query.getStatement(accessor, null, processor.getReturnedType());
|
||||
|
||||
assertEquals("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.`name`, `B`.`desc` FROM `B` "
|
||||
+ "WHERE `_class` = \"org.springframework.data.couchbase.core.Beer\"", statement.toString());
|
||||
|
||||
}
|
||||
|
||||
public static interface TestRepository extends CrudRepository<Beer, String> {
|
||||
|
||||
Page<Beer> findByNameOrderByName(String name, Pageable pageRequest);
|
||||
|
||||
Page<Beer> findByName(String name, Pageable pageRequest);
|
||||
|
||||
}
|
||||
Collection<BeerProjection> findAllProjectedBy();
|
||||
|
||||
}
|
||||
List<BeerDTO> findAllDtoedBy();
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user