DATAMONGO-1563 - Add fluent alternative for MongoOperations.

We now provide an alternative API for MongoOperations that allows defining operations in a fluent way. FluentMongoOperations reduces the number of methods and strips down the interface to a minimum while offering a more readable API.

// find all with filter query and projecting return type
template.query(Person.class)
    .matching(query(where("firstname").is("luke")))
    .as(Jedi.class)
    .all();

// insert
template.insert(Person.class)
    .inCollection(STAR_WARS)
    .one(luke);

// update with filter & upsert
template.update(Person.class)
    .apply(new Update().set("firstname", "Han"))
    .matching(query(where("id").is("han-solo")))
    .upsert();

// remove all matching
template.remove(Jedi.class)
    .inCollection(STAR_WARS)
    .matching(query(where("name").is("luke")))
    .all();

// aggregate
template.aggregateAndReturn(Jedi.class)
    .inCollection("star-wars)
    .by(newAggregation(project("name")))
    .all();

Original pull request: #466.
This commit is contained in:
Christoph Strobl
2017-05-12 13:53:21 +02:00
committed by Mark Paluch
parent 6cce16414e
commit c5f2abe037
19 changed files with 2381 additions and 9 deletions

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.util.CloseableIterator;
/**
* {@link ExecutableAggregationOperation} allows creation and execution of MongoDB aggregation operations in a fluent
* API style. <br />
* The starting {@literal domainType} is used for mapping the {@link Aggregation} provided via {@code by} into the
* MongoDB specific representation, as well as mapping back the resulting {@link org.bson.Document}. An alternative
* input type for mapping the {@link Aggregation} can be provided by using
* {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation}.
*
* <pre>
* <code>
* aggregateAndReturn(Jedi.class)
* .by(newAggregation(Human.class, project("These are not the droids you are looking for")))
* .get();
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableAggregationOperation {
/**
* Start creating an aggregation operation that returns results mapped to the given domain type. <br />
* Use {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation} to specify a potentially different
* input type for he aggregation.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link AggregationOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> AggregationOperation<T> aggregateAndReturn(Class<T> domainType);
/**
* Collection override (Optional).
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface AggregationOperationWithCollection<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link AggregationOperationWithAggregation}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
AggregationOperationWithAggregation<T> inCollection(String collection);
}
/**
* Trigger execution by calling one of the terminating methods.
*
* @param <T>
*/
interface TerminatingAggregationOperation<T> {
/**
* Apply pipeline operations as specified.
*
* @return never {@literal null}.
*/
AggregationResults<T> get();
/**
* Apply pipeline operations as specified. <br />
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor}
*
* @return a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} that needs to be closed.
* Never {@literal null}.
*/
CloseableIterator<T> stream();
}
/**
* Define the aggregation with pipeline stages.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface AggregationOperationWithAggregation<T> {
/**
* Set the aggregation to be used.
*
* @param aggregation must not be {@literal null}.
* @return new instance of {@link TerminatingAggregationOperation}.
* @throws IllegalArgumentException if aggregation is {@literal null}.
*/
TerminatingAggregationOperation<T> by(Aggregation aggregation);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface AggregationOperation<T>
extends AggregationOperationWithCollection<T>, AggregationOperationWithAggregation<T> {}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import lombok.RequiredArgsConstructor;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.util.CloseableIterator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Implementation of {@link ExecutableAggregationOperation} operating directly on {@link MongoTemplate}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableAggregationOperationSupport implements ExecutableAggregationOperation {
private final MongoTemplate template;
/**
* Create new instance of ExecutableAggregationOperationSupport.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableAggregationOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> AggregationOperation<T> aggregateAndReturn(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new AggregationOperationSupport<T>(template, null, domainType, null);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class AggregationOperationSupport<T>
implements AggregationOperationWithAggregation<T>, AggregationOperation<T>, TerminatingAggregationOperation<T> {
private final MongoTemplate template;
private final Aggregation aggregation;
private final Class<T> domainType;
private final String collection;
@Override
public AggregationOperationWithAggregation<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new AggregationOperationSupport<T>(template, aggregation, domainType, collection);
}
@Override
public TerminatingAggregationOperation<T> by(Aggregation aggregation) {
Assert.notNull(aggregation, "Aggregation must not be null!");
return new AggregationOperationSupport<T>(template, aggregation, domainType, collection);
}
@Override
public AggregationResults<T> get() {
return template.aggregate(aggregation, getCollectionName(aggregation), domainType);
}
@Override
public CloseableIterator<T> stream() {
return template.aggregateStream(aggregation, getCollectionName(aggregation), domainType);
}
private String getCollectionName(Aggregation aggregation) {
if (StringUtils.hasText(collection)) {
return collection;
}
if (aggregation instanceof TypedAggregation) {
if (((TypedAggregation<?>) aggregation).getInputType() != null) {
return template.determineCollectionName(((TypedAggregation<?>) aggregation).getInputType());
}
}
return template.determineCollectionName(domainType);
}
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.List;
import java.util.Optional;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.util.CloseableIterator;
/**
* {@link ExecutableFindOperation} allows creation and execution of MongoDB find operations in a fluent API style.
* <br />
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching} into the
* MongoDB specific representation. By default this originating {@literal domainType} is also used for mapping back the
* result from the {@link org.bson.Document}. However it is possible to define an different {@literal returnType} via
* {@code as} that is then used for mapping the result mapping. <br />
* The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
* via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
* collection name for the execution.
*
* <pre>
* <code>
* query(Human.class)
* .inCollection("star-wars")
* .as(Jedi.class)
* .matching(query(where("firstname").is("luke")))
* .all();
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableFindOperation {
/**
* Start creating a find operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link FindOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> FindOperation<T> query(Class<T> domainType);
/**
* Trigger find execution by calling one of the terminating methods.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingFindOperation<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Optional#empty()} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
Optional<T> one();
/**
* Get the first or no result.
*
* @return {@link Optional#empty()} if no match found.
*/
Optional<T> first();
/**
* Get all matching elements.
*
* @return never {@literal}.
*/
List<T> all();
/**
* Stream all matching elements.
*
* @return a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} that needs to be closed.
* Never {@literal null}.
*/
CloseableIterator<T> stream();
}
/**
* Trigger geonear execution by calling one of the terminating methods.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingFindNearOperation<T> {
/**
* Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}.
*
* @return never {@literal null}.
*/
GeoResults<T> all();
}
/**
* Terminating operations invoking the actual query execution.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperationWithQuery<T> extends TerminatingFindOperation<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindOperation}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindOperation<T> matching(Query query);
/**
* Set the filter query for the geoNear execution.
*
* @param nearQuery must not be {@literal null}.
* @return new instance of {@link TerminatingFindNearOperation}.
* @throws IllegalArgumentException if nearQuery is {@literal null}.
*/
TerminatingFindNearOperation<T> near(NearQuery nearQuery);
}
/**
* Collection override (Optional).
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperationWithCollection<T> extends FindOperationWithQuery<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link FindOperationWithProjection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
FindOperationWithProjection<T> inCollection(String collection);
}
/**
* Result type override (Optional).
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperationWithProjection<T> extends FindOperationWithQuery<T> {
/**
* Define the target type fields should be mapped to. <br />
* Skip this step if you are anyway only interested in the original domain type.
*
* @param resultType must not be {@literal null}.
* @param <R> result type.
* @return new instance of {@link FindOperationWithProjection}.
* @throws IllegalArgumentException if resultType is {@literal null}.
*/
<R> FindOperationWithQuery<R> as(Class<R> resultType);
}
/**
* {@link FindOperation} provides methods for constructing lookup operations in a fluent way.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperation<T> extends FindOperationWithCollection<T>, FindOperationWithProjection<T> {
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.Optional;
import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.data.util.CloseableIterator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.mongodb.client.FindIterable;
/**
* Implementation of {@link ExecutableFindOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableFindOperationSupport implements ExecutableFindOperation {
private final MongoTemplate template;
/**
* Create new {@link ExecutableFindOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableFindOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> FindOperation<T> query(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new FindOperationSupport<>(template, domainType, domainType, null, null);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class FindOperationSupport<T> implements FindOperation<T>, FindOperationWithCollection<T>,
FindOperationWithProjection<T>, FindOperationWithQuery<T> {
private final MongoTemplate template;
private final Class<?> domainType;
private final Class<T> returnType;
private final String collection;
private final Query query;
@Override
public FindOperationWithProjection<T> inCollection(String collection) {
Assert.hasText(collection, "Collection name must not be null nor empty!");
return new FindOperationSupport<>(template, domainType, returnType, collection, query);
}
@Override
public <T1> FindOperationWithQuery<T1> as(Class<T1> returnType) {
Assert.notNull(returnType, "ReturnType must not be null!");
return new FindOperationSupport<>(template, domainType, returnType, collection, query);
}
@Override
public TerminatingFindOperation<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new FindOperationSupport<>(template, domainType, returnType, collection, query);
}
@Override
public Optional<T> one() {
List<T> result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(2));
if (ObjectUtils.isEmpty(result)) {
return Optional.empty();
}
if (result.size() > 1) {
throw new IncorrectResultSizeDataAccessException("Query " + asString() + " returned non unique result.", 1);
}
return Optional.of(result.iterator().next());
}
@Override
public Optional<T> first() {
List<T> result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(1));
return ObjectUtils.isEmpty(result) ? Optional.empty() : Optional.of(result.iterator().next());
}
@Override
public List<T> all() {
return doFind(null);
}
@Override
public CloseableIterator<T> stream() {
return doStream();
}
@Override
public TerminatingFindNearOperation<T> near(NearQuery nearQuery) {
return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType);
}
private List<T> doFind(CursorPreparer preparer) {
Document queryObject = query != null ? query.getQueryObject() : new Document();
Document fieldsObject = query != null ? query.getFieldsObject() : new Document();
return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType,
getCursorPreparer(query, preparer));
}
private CloseableIterator<T> doStream() {
return template.doStream(query != null ? query : new BasicQuery(new Document()), domainType, getCollectionName(),
returnType);
}
private CursorPreparer getCursorPreparer(Query query, CursorPreparer preparer) {
return query == null || preparer != null ? preparer : template.new QueryCursorPreparer(query, domainType);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
private String asString() {
return SerializationUtils.serializeToJsonSafely(query);
}
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
static class DelegatingQueryCursorPreparer implements CursorPreparer {
private final CursorPreparer delegate;
private Optional<Integer> limit = Optional.empty();
public DelegatingQueryCursorPreparer(CursorPreparer delegate) {
this.delegate = delegate;
}
@Override
public FindIterable<Document> prepare(FindIterable<Document> cursor) {
FindIterable<Document> target = delegate.prepare(cursor);
return limit.map(it -> target.limit(it)).orElse(target);
}
CursorPreparer limit(int limit) {
this.limit = Optional.of(limit);
return this;
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.Collection;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import com.mongodb.bulk.BulkWriteResult;
/**
* {@link ExecutableFindOperation} allows creation and execution of MongoDB insert and bulk insert operations in a
* fluent API style. <br />
* The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
* via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
* collection name for the execution.
*
* <pre>
* <code>
* insert(Jedi.class)
* .inCollection("star-wars")
* .one(luke);
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableInsertOperation {
/**
* Start creating an insert operation for given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link InsertOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> InsertOperation<T> insert(Class<T> domainType);
/**
* Trigger insert execution by calling one of the terminating methods.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingInsertOperation<T> extends TerminatingBulkInsertOperation<T> {
/**
* Insert exactly one object.
*
* @param object must not be {@literal null}.
* @throws IllegalArgumentException if object is {@literal null}.
*/
void one(T object);
/**
* Insert a collection of objects.
*
* @param objects must not be {@literal null}.
* @throws IllegalArgumentException if objects is {@literal null}.
*/
void all(Collection<? extends T> objects);
}
/**
* Trigger bulk insert execution by calling one of the terminating methods.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingBulkInsertOperation<T> {
/**
* Bulk write collection of objects.
*
* @param objects must not be {@literal null}.
* @return resulting {@link BulkWriteResult}.
* @throws IllegalArgumentException if objects is {@literal null}.
*/
BulkWriteResult bulk(Collection<? extends T> objects);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface InsertOperation<T>
extends TerminatingInsertOperation<T>, InsertOperationWithCollection<T>, InsertOperationWithBulkMode<T> {
}
/**
* Collection override (Optional).
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface InsertOperationWithCollection<T> {
/**
* Explicitly set the name of the collection. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link InsertOperationWithBulkMode}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
InsertOperationWithBulkMode<T> inCollection(String collection);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface InsertOperationWithBulkMode<T> extends TerminatingInsertOperation<T> {
/**
* Define the {@link BulkMode} to use for bulk insert operation.
*
* @param mode must not be {@literal null}.
* @return new instance of {@link TerminatingBulkInsertOperation}.
* @throws IllegalArgumentException if bulkMode is {@literal null}.
*/
TerminatingBulkInsertOperation<T> withBulkMode(BulkMode bulkMode);
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.bulk.BulkWriteResult;
/**
* Implementation of {@link ExecutableInsertOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
private final MongoTemplate template;
/**
* Create new {@link ExecutableInsertOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableInsertOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> InsertOperation<T> insert(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new InsertOperationSupport<T>(template, domainType, null, null);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class InsertOperationSupport<T> implements InsertOperation<T> {
private final MongoTemplate template;
private final Class<T> domainType;
private final String collection;
private final BulkMode bulkMode;
@Override
public void one(T object) {
Assert.notNull(object, "Object must not be null!");
template.insert(object, getCollectionName());
}
@Override
public void all(Collection<? extends T> objects) {
Assert.notNull(objects, "Objects must not be null!");
template.insert(objects, getCollectionName());
}
@Override
public BulkWriteResult bulk(Collection<? extends T> objects) {
Assert.notNull(objects, "Objects must not be null!");
return template.bulkOps(bulkMode != null ? bulkMode : BulkMode.ORDERED, domainType, getCollectionName())
.insert(new ArrayList<>(objects)).execute();
}
@Override
public InsertOperationWithBulkMode inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new InsertOperationSupport<T>(template, domainType, collection, bulkMode);
}
@Override
public TerminatingBulkInsertOperation withBulkMode(BulkMode bulkMode) {
Assert.notNull(bulkMode, "BulkMode must not be null!");
return new InsertOperationSupport<T>(template, domainType, collection, bulkMode);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.List;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.result.DeleteResult;
/**
* {@link ExecutableRemoveOperation} allows creation and execution of MongoDB remove / findAndRemove operations in a
* fluent API style. <br />
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching} into the
* MongoDB specific representation. The collection to operate on is by default derived from the initial
* {@literal domainType} and can be defined there via {@link org.springframework.data.mongodb.core.mapping.Document}.
* Using {@code inCollection} allows to override the collection name for the execution.
*
* <pre>
* <code>
* remove(Jedi.class)
* .inCollection("star-wars")
* .matching(query(where("firstname").is("luke")))
* .all();
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableRemoveOperation {
/**
* Start creating a remove operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link RemoveOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> RemoveOperation<T> remove(Class<T> domainType);
/**
* Collection override (Optional).
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface RemoveOperationWithCollection<T> extends RemoveOperationWithQuery<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link RemoveOperationWithCollection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
RemoveOperationWithQuery<T> inCollection(String collection);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingRemoveOperation<T> {
/**
* Remove all documents matching.
*
* @return
*/
DeleteResult all();
/**
* Remove and return all matching documents. <br/>
* <strong>NOTE</strong> The entire list of documents will be fetched before sending the actual delete commands.
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
* operation.
*
* @return empty {@link List} if no match found. Never {@literal null}.
*/
List<T> findAndRemove();
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface RemoveOperationWithQuery<T> extends TerminatingRemoveOperation<T> {
/**
* Define the query filtering elements.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingRemoveOperation}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingRemoveOperation<T> matching(Query query);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface RemoveOperation<T> extends RemoveOperationWithCollection<T> {
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import lombok.RequiredArgsConstructor;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.client.result.DeleteResult;
/**
* Implementation of {@link ExecutableRemoveOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
private final MongoTemplate tempate;
/**
* Create new {@link ExecutableRemoveOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableRemoveOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.tempate = template;
}
@Override
public <T> RemoveOperation<T> remove(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new RemoveOperationSupport<>(tempate, null, domainType, null);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class RemoveOperationSupport<T> implements RemoveOperation<T>, RemoveOperationWithCollection<T> {
private final MongoTemplate template;
private final Query query;
private final Class<T> domainType;
private final String collection;
@Override
public RemoveOperationWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new RemoveOperationSupport<>(template, query, domainType, collection);
}
@Override
public TerminatingRemoveOperation<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new RemoveOperationSupport<>(template, query, domainType, collection);
}
@Override
public DeleteResult all() {
String collectionName = StringUtils.hasText(collection) ? collection
: template.determineCollectionName(domainType);
return template.doRemove(collectionName, query != null ? query : new BasicQuery(new Document()), domainType);
}
@Override
public List<T> findAndRemove() {
String collectionName = StringUtils.hasText(collection) ? collection
: template.determineCollectionName(domainType);
return template.doFindAndDelete(collectionName, query != null ? query : new BasicQuery(new Document()),
domainType);
}
}
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.Optional;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import com.mongodb.client.result.UpdateResult;
/**
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify operations in a
* fluent API style. <br />
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
* the {@link Update} via {@code apply} into the MongoDB specific representations. The collection to operate on is by
* default derived from the initial {@literal domainType} and can be defined there via
* {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
* collection name for the execution.
*
* <pre>
* <code>
* update(Jedi.class)
* .inCollection("star-wars")
* .matching(query(where("firstname").is("luke")))
* .apply(new Update().set("lastname", "skywalker"))
* .upsert();
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableUpdateOperation {
/**
* Start creating an update operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> UpdateOperation<T> update(Class<T> domainType);
interface UpdateOperation<T>
extends UpdateOperationWithCollection<T>, UpdateOperationWithQuery<T>, UpdateOperationWithUpdate<T> {
}
/**
* Declare the {@link Update} to apply.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateOperationWithUpdate<T> {
/**
* Set the {@link Update} to be applied.
*
* @param update must not be {@literal null}.
* @return new instance of {@link TerminatingUpdateOperation}.
* @throws IllegalArgumentException if update is {@literal null}.
*/
TerminatingUpdateOperation<T> apply(Update update);
}
/**
* Explicitly define the name of the collection to perform operation in.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateOperationWithCollection<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link UpdateOperationWithCollection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
UpdateOperationWithQuery<T> inCollection(String collection);
}
/**
* Define a filter query for the {@link Update}.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateOperationWithQuery<T> extends UpdateOperationWithUpdate<T> {
/**
* Filter documents by given {@literal query}.
*
* @param query must not be {@literal null}.
* @return new instance of {@link UpdateOperationWithQuery}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
UpdateOperationWithUpdate<T> matching(Query query);
}
/**
* Define {@link FindAndModifyOptions}.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface FindAndModifyWithOptions<T> {
/**
* Explicitly define {@link FindAndModifyOptions} for the {@link Update}.
*
* @param options must not be {@literal null}.
* @return new instance of {@link FindAndModifyWithOptions}.
* @throws IllegalArgumentException if options is {@literal null}.
*/
TerminatingFindAndModifyOperation<T> withOptions(FindAndModifyOptions options);
}
/**
* Trigger findAndModify execution by calling one of the terminating methods.
*
* @param <T>
*/
interface TerminatingFindAndModifyOperation<T> {
/**
* Find, modify and return the first matching document.
*
* @return {@link Optional#empty()} if nothing found.
*/
Optional<T> findAndModify();
}
/**
* Trigger update execution by calling one of the terminating methods.
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingUpdateOperation<T> extends TerminatingFindAndModifyOperation<T>, FindAndModifyWithOptions<T> {
/**
* Update all matching documents in the collection.
*
* @return never {@literal null}.
*/
UpdateResult all();
/**
* Update the first document in the collection.
*
* @return never {@literal null}.
*/
UpdateResult first();
/**
* Creates a new document if no documents match the filter query or updates the matching ones.
*
* @return never {@literal null}.
*/
UpdateResult upsert();
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import lombok.RequiredArgsConstructor;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.client.result.UpdateResult;
/**
* Implementation of {@link ExecutableUpdateOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
private final MongoTemplate template;
/**
* Creates new {@link ExecutableUpdateOperationSupport}.
*
* @param template must not be {@literal null}.
*/
ExecutableUpdateOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> UpdateOperation<T> update(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new UpdateOperationSupport<T>(template, null, domainType, null, null, null);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class UpdateOperationSupport<T> implements UpdateOperation<T>, UpdateOperationWithCollection<T>,
UpdateOperationWithQuery<T>, TerminatingUpdateOperation<T> {
private final MongoTemplate template;
private final Query query;
private final Class<T> domainType;
private final Update update;
private final String collection;
private final FindAndModifyOptions options;
@Override
public TerminatingUpdateOperation<T> apply(Update update) {
Assert.notNull(update, "Update must not be null!");
return new UpdateOperationSupport<T>(template, query, domainType, update, collection, options);
}
@Override
public UpdateOperationWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new UpdateOperationSupport<T>(template, query, domainType, update, collection, options);
}
@Override
public UpdateResult first() {
return doUpdate(false, false);
}
@Override
public UpdateResult upsert() {
return doUpdate(true, true);
}
@Override
public Optional<T> findAndModify() {
String collectionName = StringUtils.hasText(collection) ? collection
: template.determineCollectionName(domainType);
return Optional.ofNullable(template.findAndModify(query != null ? query : new BasicQuery(new Document()), update,
options, domainType, collectionName));
}
@Override
public UpdateOperationWithUpdate<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new UpdateOperationSupport<T>(template, query, domainType, update, collection, options);
}
@Override
public UpdateResult all() {
return doUpdate(true, false);
}
@Override
public TerminatingFindAndModifyOperation<T> withOptions(FindAndModifyOptions options) {
Assert.notNull(options, "Options must not be null!");
return new UpdateOperationSupport<T>(template, query, domainType, update, collection, options);
}
private UpdateResult doUpdate(boolean multi, boolean upsert) {
String collectionName = StringUtils.hasText(collection) ? collection
: template.determineCollectionName(domainType);
Query query = this.query != null ? this.query : new BasicQuery(new Document());
return template.doUpdate(collectionName, query, update, domainType, upsert, multi);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
/**
* Stripped down interface providing access to a fluent API that specifies a basic set of MongoDB operations.
*
* @author Christoph Strobl
* @since 2.0
*/
public interface FluentMongoOperations extends ExecutableFindOperation, ExecutableInsertOperation,
ExecutableUpdateOperation, ExecutableRemoveOperation, ExecutableAggregationOperation {
}

View File

@@ -58,7 +58,7 @@ import com.mongodb.client.result.UpdateResult;
* @author Thomas Darimont
* @author Maninder Singh
*/
public interface MongoOperations {
public interface MongoOperations extends FluentMongoOperations {
/**
* The collection name used for the specified class by this template.

View File

@@ -344,10 +344,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
@Override
public <T> CloseableIterator<T> stream(final Query query, final Class<T> entityType, final String collectionName) {
return doStream(query, entityType, collectionName, entityType);
}
protected <T> CloseableIterator<T> doStream(final Query query, final Class<?> entityType, final String collectionName,
Class<T> returnType) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(entityType, "Entity type must not be null!");
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Assert.notNull(returnType, "ReturnType must not be null!");
return execute(collectionName, new CollectionCallback<CloseableIterator<T>>() {
@@ -364,7 +370,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
.prepare(collection.find(mappedQuery).projection(mappedFields));
return new CloseableIterableCursorAdapter<T>(cursor, exceptionTranslator,
new ReadDocumentCallback<T>(mongoConverter, entityType, collectionName));
new ReadDocumentCallback<T>(mongoConverter, returnType, collectionName));
}
});
}
@@ -644,17 +650,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
@SuppressWarnings("unchecked")
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass, String collectionName) {
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> domainType, String collectionName) {
return geoNear(near, domainType, collectionName, domainType);
}
public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String collectionName, Class<T> returnType) {
if (near == null) {
throw new InvalidDataAccessApiUsageException("NearQuery must not be null!");
}
if (entityClass == null) {
if (domainType == null) {
throw new InvalidDataAccessApiUsageException("Entity class must not be null!");
}
String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass);
String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(domainType);
Document nearDocument = near.toDocument();
Document command = new Document("geoNear", collection);
@@ -662,12 +672,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (nearDocument.containsKey("query")) {
Document query = (Document) nearDocument.get("query");
command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass)));
command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(domainType)));
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command),
entityClass, collectionName);
domainType, collectionName);
}
Document commandResult = executeCommand(command, this.readPreference);
@@ -675,7 +685,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
results = results == null ? Collections.emptyList() : results;
DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<T>(
new ReadDocumentCallback<T>(mongoConverter, entityClass, collectionName), near.getMetric());
new ReadDocumentCallback<T>(mongoConverter, returnType, collectionName), near.getMetric());
List<GeoResult<T>> result = new ArrayList<GeoResult<T>>(results.size());
int index = 0;
@@ -1776,6 +1786,31 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
});
}
@Override
public <T> FindOperation<T> query(Class<T> domainType) {
return new ExecutableFindOperationSupport(this).query(domainType);
}
@Override
public <T> UpdateOperation<T> update(Class<T> domainType) {
return new ExecutableUpdateOperationSupport(this).update(domainType);
}
@Override
public <T> RemoveOperation<T> remove(Class<T> domainType) {
return new ExecutableRemoveOperationSupport(this).remove(domainType);
}
@Override
public <T> AggregationOperation<T> aggregateAndReturn(Class<T> domainType) {
return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType);
}
@Override
public <T> InsertOperation<T> insert(Class<T> domainType) {
return new ExecutableInsertOperationSupport(this).insert(domainType);
}
/**
* Assert that the {@link Document} does not enable Aggregation explain mode.
*
@@ -1958,6 +1993,38 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
collectionName);
}
/**
* Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified targetClass while
* using sourceClass for mapping the query.
*
* @param collectionName
* @param query
* @param fields
* @param sourceClass
* @param targetClass
* @param objectCallback
* @param <S>
* @param <T>
* @return
* @since 2.0
*/
<S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> sourceClass,
Class<T> targetClass, CursorPreparer preparer) {
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(sourceClass);
Document mappedFields = queryMapper.getMappedFields(fields, entity);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}",
serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName);
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer,
new ReadDocumentCallback<T>(mongoConverter, targetClass, collectionName), collectionName);
}
protected Document convertToDocument(CollectionOptions collectionOptions) {
Document document = new Document();
@@ -2674,4 +2741,5 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public MongoDbFactory getMongoDbFactory() {
return mongoDbFactory;
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class ExecutableAggregationOperationSupportUnitTests {
@Mock MongoTemplate template;
ExecutableAggregationOperationSupport opSupport;
@Before
public void setUp() {
opSupport = new ExecutableAggregationOperationSupport(template);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void throwsExceptionOnNullDomainType() {
opSupport.aggregateAndReturn(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void throwsExceptionOnNullCollectionWhenUsed() {
opSupport.aggregateAndReturn(Person.class).inCollection(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void throwsExceptionOnEmptyCollectionWhenUsed() {
opSupport.aggregateAndReturn(Person.class).inCollection("");
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void throwsExceptionOnNullAggregation() {
opSupport.aggregateAndReturn(Person.class).by(null);
}
@Test // DATAMONGO-1563
public void aggregateWithUntypedAggregationAndExplicitCollection() {
opSupport.aggregateAndReturn(Person.class).inCollection("star-wars").by(newAggregation(project("foo"))).get();
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).aggregate(any(Aggregation.class), eq("star-wars"), captor.capture());
assertThat(captor.getValue()).isEqualTo(Person.class);
}
@Test // DATAMONGO-1563
public void aggregateWithUntypedAggregation() {
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
opSupport.aggregateAndReturn(Person.class).by(newAggregation(project("foo"))).get();
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).determineCollectionName(captor.capture());
verify(template).aggregate(any(Aggregation.class), eq("person"), captor.capture());
assertThat(captor.getAllValues()).containsExactly(Person.class, Person.class);
}
@Test // DATAMONGO-1563
public void aggregateWithTypeAggregation() {
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
opSupport.aggregateAndReturn(Jedi.class).by(newAggregation(Person.class, project("foo"))).get();
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).determineCollectionName(captor.capture());
verify(template).aggregate(any(Aggregation.class), eq("person"), captor.capture());
assertThat(captor.getAllValues()).containsExactly(Person.class, Jedi.class);
}
@Test // DATAMONGO-1563
public void aggregateStreamWithUntypedAggregationAndExplicitCollection() {
opSupport.aggregateAndReturn(Person.class).inCollection("star-wars").by(newAggregation(project("foo"))).stream();
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).aggregateStream(any(Aggregation.class), eq("star-wars"), captor.capture());
assertThat(captor.getValue()).isEqualTo(Person.class);
}
@Test // DATAMONGO-1563
public void aggregateStreamWithUntypedAggregation() {
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
opSupport.aggregateAndReturn(Person.class).by(newAggregation(project("foo"))).stream();
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).determineCollectionName(captor.capture());
verify(template).aggregateStream(any(Aggregation.class), eq("person"), captor.capture());
assertThat(captor.getAllValues()).containsExactly(Person.class, Person.class);
}
@Test // DATAMONGO-1563
public void aggregateStreamWithTypeAggregation() {
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
opSupport.aggregateAndReturn(Jedi.class).by(newAggregation(Person.class, project("foo"))).stream();
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).determineCollectionName(captor.capture());
verify(template).aggregateStream(any(Aggregation.class), eq("person"), captor.capture());
assertThat(captor.getAllValues()).containsExactly(Person.class, Jedi.class);
}
static class Person {
}
static class Jedi {}
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.Before;
import org.junit.Test;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.util.CloseableIterator;
import com.mongodb.MongoClient;
/**
* @author Christoph Strobl
*/
public class ExecutableFindOperationSupportTests {
private static final String STAR_WARS = "star-wars";
MongoTemplate template;
Person han;
Person luke;
@Before
public void setUp() {
template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableFindOperationSupportTests"));
template.dropCollection(STAR_WARS);
han = new Person();
han.firstname = "han";
han.id = "id-1";
luke = new Person();
luke.firstname = "luke";
luke.id = "id-2";
template.save(han);
template.save(luke);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void domainTypeIsRequired() {
template.query(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void returnTypeIsRequiredOnSet() {
template.query(Person.class).as(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void collectionIsRequiredOnSet() {
template.query(Person.class).inCollection(null);
}
@Test // DATAMONGO-1563
public void findAll() {
assertThat(template.query(Person.class).all()).containsExactlyInAnyOrder(han, luke);
}
@Test // DATAMONGO-1563
public void findAllWithCollection() {
assertThat(template.query(Human.class).inCollection(STAR_WARS).all()).hasSize(2);
}
@Test // DATAMONGO-1563
public void findAllWithProjection() {
assertThat(template.query(Person.class).as(Jedi.class).all()).hasOnlyElementsOfType(Jedi.class)
.hasSize(2);
}
@Test // DATAMONGO-1563
public void findAllBy() {
assertThat(template.query(Person.class).matching(query(where("firstname").is("luke"))).all())
.containsExactlyInAnyOrder(luke);
}
@Test // DATAMONGO-1563
public void findAllByWithCollectionUsingMappingInformation() {
assertThat(template.query(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))).all()).hasSize(1)
.hasOnlyElementsOfType(Jedi.class);
}
@Test // DATAMONGO-1563
public void findAllByWithCollection() {
assertThat(template.query(Human.class).inCollection(STAR_WARS).matching(query(where("firstname").is("luke"))).all())
.hasSize(1);
}
@Test // DATAMONGO-1563
public void findAllByWithProjection() {
assertThat(template.query(Person.class).as(Jedi.class).matching(query(where("firstname").is("luke"))).all())
.hasOnlyElementsOfType(Jedi.class).hasSize(1);
}
@Test // DATAMONGO-1563
public void findBy() {
assertThat(template.query(Person.class).matching(query(where("firstname").is("luke"))).one()).contains(luke);
}
@Test // DATAMONGO-1563
public void findByNoMatch() {
assertThat(template.query(Person.class).matching(query(where("firstname").is("spock"))).one()).isEmpty();
}
@Test(expected = IncorrectResultSizeDataAccessException.class) // DATAMONGO-1563
public void findByTooManyResults() {
template.query(Person.class).matching(query(where("firstname").in("han", "luke"))).one();
}
@Test // DATAMONGO-1563
public void streamAll() {
try (CloseableIterator<Person> stream = template.query(Person.class).stream()) {
assertThat(stream).containsExactlyInAnyOrder(han, luke);
}
}
@Test // DATAMONGO-1563
public void streamAllWithCollection() {
try (CloseableIterator<Human> stream = template.query(Human.class).inCollection(STAR_WARS).stream()) {
assertThat(stream).hasSize(2);
}
}
@Test // DATAMONGO-1563
public void streamAllWithProjection() {
try (CloseableIterator<Jedi> stream = template.query(Person.class).as(Jedi.class).stream()) {
assertThat(stream).hasOnlyElementsOfType(Jedi.class).hasSize(2);
}
}
@Test // DATAMONGO-1563
public void streamAllBy() {
try (CloseableIterator<Person> stream = template.query(Person.class)
.matching(query(where("firstname").is("luke"))).stream()) {
assertThat(stream).containsExactlyInAnyOrder(luke);
}
}
@Test // DATAMONGO-1563
public void findAllNearBy() {
template.indexOps(Planet.class).ensureIndex(
new GeospatialIndex("coordinates").typed(GeoSpatialIndexType.GEO_2DSPHERE).named("planet-coordinate-idx"));
Planet alderan = new Planet("alderan", new Point(-73.9836, 40.7538));
Planet dantooine = new Planet("dantooine", new Point(-73.9928, 40.7193));
template.save(alderan);
template.save(dantooine);
GeoResults<Planet> results = template.query(Planet.class)
.near(NearQuery.near(-73.9667, 40.78).spherical(true)).all();
assertThat(results.getContent()).hasSize(2);
assertThat(results.getContent().get(0).getDistance()).isNotNull();
}
@Test // DATAMONGO-1563
public void findAllNearByWithCollectionAndProjection() {
template.indexOps(Planet.class).ensureIndex(
new GeospatialIndex("coordinates").typed(GeoSpatialIndexType.GEO_2DSPHERE).named("planet-coordinate-idx"));
Planet alderan = new Planet("alderan", new Point(-73.9836, 40.7538));
Planet dantooine = new Planet("dantooine", new Point(-73.9928, 40.7193));
template.save(alderan);
template.save(dantooine);
GeoResults<Human> results = template.query(Object.class).inCollection(STAR_WARS).as(Human.class)
.near(NearQuery.near(-73.9667, 40.78).spherical(true)).all();
assertThat(results.getContent()).hasSize(2);
assertThat(results.getContent().get(0).getDistance()).isNotNull();
assertThat(results.getContent().get(0).getContent()).isInstanceOf(Human.class);
assertThat(results.getContent().get(0).getContent().getId()).isEqualTo("alderan");
}
@Data
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
static class Person {
@Id String id;
String firstname;
}
@Data
static class Human {
@Id String id;
}
@Data
static class Jedi {
@Field("firstname") String name;
}
@Data
@AllArgsConstructor
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
static class Planet {
@Id String name;
Point coordinates;
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.anyList;
import lombok.Data;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
/**
* @author Christoph Strobl
* @since 2017/06
*/
@RunWith(MockitoJUnitRunner.Silent.class)
public class ExecutableInsertOperationSupportUnitTests {
private static final String STAR_WARS = "star-wars";
@Mock MongoTemplate template;
@Mock BulkOperations bulkOperations;
ExecutableInsertOperationSupport ops;
Person luke, han;
@Before
public void setUp() {
when(template.bulkOps(any(), any(), any())).thenReturn(bulkOperations);
when(template.determineCollectionName(any(Class.class))).thenReturn(STAR_WARS);
when(bulkOperations.insert(anyList())).thenReturn(bulkOperations);
ops = new ExecutableInsertOperationSupport(template);
luke = new Person();
luke.id = "id-1";
luke.firstname = "luke";
han = new Person();
han.firstname = "han";
han.id = "id-2";
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void nullCollectionShouldThrowException() {
ops.insert(Person.class).inCollection(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void nullBulkModeShouldThrowException() {
ops.insert(Person.class).withBulkMode(null);
}
@Test // DATAMONGO-1563
public void insertShouldUseDerivedCollectionName() {
ops.insert(Person.class).one(luke);
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).determineCollectionName(captor.capture());
verify(template).insert(eq(luke), eq(STAR_WARS));
assertThat(captor.getAllValues()).containsExactly(Person.class);
}
@Test // DATAMONGO-1563
public void insertShouldUseExplicitCollectionName() {
ops.insert(Person.class).inCollection(STAR_WARS).one(luke);
verify(template, never()).determineCollectionName(any(Class.class));
verify(template).insert(eq(luke), eq(STAR_WARS));
}
@Test // DATAMONGO-1563
public void insertCollectionShouldDelegateCorrectly() {
ops.insert(Person.class).all(Arrays.asList(luke, han));
verify(template).determineCollectionName(any(Class.class));
verify(template).insert(anyList(), eq(STAR_WARS));
}
@Test // DATAMONGO-1563
public void bulkInsertCollectionShouldDelegateCorrectly() {
ops.insert(Person.class).bulk(Arrays.asList(luke, han));
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).determineCollectionName(any(Class.class));
verify(template).bulkOps(eq(BulkMode.ORDERED), captor.capture(), eq(STAR_WARS));
verify(bulkOperations).insert(anyList());
verify(bulkOperations).execute();
}
@Test // DATAMONGO-1563
public void bulkInsertWithBulkModeShouldDelegateCorrectly() {
ops.insert(Person.class).withBulkMode(BulkMode.UNORDERED).bulk(Arrays.asList(luke, han));
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(template).determineCollectionName(any(Class.class));
verify(template).bulkOps(eq(BulkMode.UNORDERED), captor.capture(), eq(STAR_WARS));
verify(bulkOperations).insert(anyList());
verify(bulkOperations).execute();
}
@Data
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
static class Person {
@Id String id;
String firstname;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import lombok.Data;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Field;
import com.mongodb.MongoClient;
import com.mongodb.client.result.DeleteResult;
/**
* @author Christoph Strobl
*/
public class ExecutableRemoveOperationSupportTests {
private static final String STAR_WARS = "star-wars";
MongoTemplate template;
Person han;
Person luke;
@Before
public void setUp() {
template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableRemoveOperationSupportTests"));
template.dropCollection(STAR_WARS);
han = new Person();
han.firstname = "han";
han.id = "id-1";
luke = new Person();
luke.firstname = "luke";
luke.id = "id-2";
template.save(han);
template.save(luke);
}
@Test // DATAMONGO-1563
public void removeAll() {
DeleteResult result = template.remove(Person.class).all();
assertThat(result.getDeletedCount()).isEqualTo(2L);
}
@Test // DATAMONGO-1563
public void removeAllMatching() {
DeleteResult result = template.remove(Person.class).matching(query(where("firstname").is("han"))).all();
assertThat(result.getDeletedCount()).isEqualTo(1L);
}
@Test // DATAMONGO-1563
public void removeAllMatchingWithAlternateDomainTypeAndCollection() {
DeleteResult result = template.remove(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke")))
.all();
assertThat(result.getDeletedCount()).isEqualTo(1L);
}
@Test // DATAMONGO-1563
public void removeAndReturnAllMatching() {
List<Person> result = template.remove(Person.class).matching(query(where("firstname").is("han"))).findAndRemove();
assertThat(result).containsExactly(han);
}
@Data
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
static class Person {
@Id String id;
String firstname;
}
@Data
static class Jedi {
@Field("firstname") String name;
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import lombok.Data;
import java.util.Optional;
import org.bson.BsonString;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import com.mongodb.MongoClient;
import com.mongodb.client.result.UpdateResult;
/**
* @author Christoph Strobl
*/
public class ExecutableUpdateOperationSupportTests {
private static final String STAR_WARS = "star-wars";
MongoTemplate template;
Person han;
Person luke;
@Before
public void setUp() {
template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableUpdateOperationSupportTests"));
template.dropCollection(STAR_WARS);
han = new Person();
han.firstname = "han";
han.id = "id-1";
luke = new Person();
luke.firstname = "luke";
luke.id = "id-2";
template.save(han);
template.save(luke);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void domainTypeIsRequired() {
template.update(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void updateIsRequired() {
template.update(Person.class).apply(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void collectionIsRequiredOnSet() {
template.update(Person.class).inCollection(null);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1563
public void findAndModifyOptionsAreRequiredOnSet() {
template.update(Person.class).apply(new Update()).withOptions(null);
}
@Test // DATAMONGO-1563
public void updateFirst() {
UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Han")).first();
assertThat(result.getModifiedCount()).isEqualTo(1L);
assertThat(result.getUpsertedId()).isNull();
}
@Test // DATAMONGO-1563
public void updateAll() {
UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Han")).all();
assertThat(result.getModifiedCount()).isEqualTo(2L);
assertThat(result.getUpsertedId()).isNull();
}
@Test // DATAMONGO-1563
public void updateAllMatching() {
UpdateResult result = template.update(Person.class).matching(queryHan()).apply(new Update().set("firstname", "Han"))
.all();
assertThat(result.getModifiedCount()).isEqualTo(1L);
assertThat(result.getUpsertedId()).isNull();
}
@Test // DATAMONGO-1563
public void updateWithDifferentDomainClassAndCollection() {
UpdateResult result = template.update(Jedi.class).inCollection(STAR_WARS)
.matching(query(where("_id").is(han.getId()))).apply(new Update().set("name", "Han")).all();
assertThat(result.getModifiedCount()).isEqualTo(1L);
assertThat(result.getUpsertedId()).isNull();
assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname",
"Han");
}
@Test // DATAMONGO-1563
public void findAndModify() {
Optional<Person> result = template.update(Person.class).matching(queryHan())
.apply(new Update().set("firstname", "Han")).findAndModify();
assertThat(result).contains(han);
assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname",
"Han");
}
@Test // DATAMONGO-1563
public void findAndModifyWithDifferentDomainTypeAndCollection() {
Optional<Jedi> result = template.update(Jedi.class).inCollection(STAR_WARS)
.matching(query(where("_id").is(han.getId()))).apply(new Update().set("name", "Han")).findAndModify();
assertThat(result.get()).hasFieldOrPropertyWithValue("name", "han");
assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname",
"Han");
}
@Test // DATAMONGO-1563
public void findAndModifyWithOptions() {
Optional<Person> result = template.update(Person.class).matching(queryHan())
.apply(new Update().set("firstname", "Han")).withOptions(FindAndModifyOptions.options().returnNew(true))
.findAndModify();
assertThat(result.get()).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han");
}
@Test // DATAMONGO-1563
public void upsert() {
UpdateResult result = template.update(Person.class).matching(query(where("id").is("id-3")))
.apply(new Update().set("firstname", "Chewbacca")).upsert();
assertThat(result.getModifiedCount()).isEqualTo(0L);
assertThat(result.getUpsertedId()).isEqualTo(new BsonString("id-3"));
}
private Query queryHan() {
return query(where("id").is(han.getId()));
}
@Data
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
static class Person {
@Id String id;
String firstname;
}
@Data
static class Human {
@Id String id;
}
@Data
static class Jedi {
@Field("firstname") String name;
}
}

View File

@@ -1159,7 +1159,7 @@ public class MongoTemplateTests {
@Test // DATADOC-166
public void removingNullIsANoOp() {
template.remove(null);
template.remove((Object) null);
}
@Test // DATADOC-240, DATADOC-212