DATAMONGO-2416 - Add convenience methods accepting CriteriaDefinition on Fluent API.

The fluent API now exposes default interface methods accepting CriteriaDefinition for a more concise expression of queries that do not require a Query object.

template.query(Person.class).matching(where("firstname").is("luke")).all()

instead of

template.query(Person.class).matching(query(where("firstname").is("luke"))).all()

Original Pull Request: #840
This commit is contained in:
Mark Paluch
2020-03-10 14:44:38 +01:00
committed by Christoph Strobl
parent 5fb4b036bb
commit 6387eb9762
19 changed files with 195 additions and 6 deletions

View File

@@ -21,6 +21,7 @@ import java.util.stream.Stream;
import org.springframework.dao.DataAccessException;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
@@ -170,6 +171,18 @@ public interface ExecutableFindOperation {
*/
TerminatingFind<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link TerminatingFind}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default TerminatingFind<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
/**
* Set the filter query for the geoNear execution.
*

View File

@@ -19,6 +19,7 @@ import java.util.List;
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
/**
@@ -30,7 +31,7 @@ import org.springframework.data.mongodb.core.query.Query;
* 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>
* mapReduce(Human.class)
@@ -44,6 +45,7 @@ import org.springframework.data.mongodb.core.query.Query;
* </pre>
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.1
*/
public interface ExecutableMapReduceOperation {
@@ -146,6 +148,18 @@ public interface ExecutableMapReduceOperation {
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingMapReduce<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link TerminatingMapReduce}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default TerminatingMapReduce<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
}
/**

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
import java.util.List;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.result.DeleteResult;
@@ -119,6 +120,18 @@ public interface ExecutableRemoveOperation {
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingRemove<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link TerminatingRemove}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default TerminatingRemove<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
}
/**

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import java.util.Optional;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
@@ -210,6 +211,18 @@ public interface ExecutableUpdateOperation {
* @throws IllegalArgumentException if query is {@literal null}.
*/
UpdateWithUpdate<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link UpdateWithUpdate}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default UpdateWithUpdate<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
}
/**

View File

@@ -19,6 +19,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
@@ -144,6 +145,18 @@ public interface ReactiveFindOperation {
*/
TerminatingFind<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link TerminatingFind}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default TerminatingFind<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
/**
* Set the filter query for the geoNear execution.
*

View File

@@ -19,6 +19,7 @@ import reactor.core.publisher.Flux;
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
/**
@@ -30,7 +31,7 @@ import org.springframework.data.mongodb.core.query.Query;
* 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>
* mapReduce(Human.class)
@@ -146,6 +147,18 @@ public interface ReactiveMapReduceOperation {
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingMapReduce<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link TerminatingMapReduce}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default TerminatingMapReduce<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
}
/**

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.result.DeleteResult;
@@ -106,6 +107,18 @@ public interface ReactiveRemoveOperation {
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingRemove<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link TerminatingRemove}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default TerminatingRemove<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
}
interface ReactiveRemove<T> extends RemoveWithCollection<T> {}

View File

@@ -15,12 +15,13 @@
*/
package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import reactor.core.publisher.Mono;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import com.mongodb.client.result.UpdateResult;
@@ -171,6 +172,18 @@ public interface ReactiveUpdateOperation {
* @throws IllegalArgumentException if query is {@literal null}.
*/
UpdateWithUpdate<T> matching(Query query);
/**
* Set the filter {@link CriteriaDefinition criteria} to be used.
*
* @param criteriaDefinition must not be {@literal null}.
* @return new instance of {@link UpdateWithUpdate}.
* @throws IllegalArgumentException if query is {@literal null}.
* @since 3.0
*/
default UpdateWithUpdate<T> matching(CriteriaDefinition criteriaDefinition) {
return matching(Query.query(criteriaDefinition));
}
}
/**

View File

@@ -95,6 +95,8 @@ public class Query {
*/
public Query addCriteria(CriteriaDefinition criteriaDefinition) {
Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!");
CriteriaDefinition existing = this.criteria.get(criteriaDefinition.getKey());
String key = criteriaDefinition.getKey();

View File

@@ -169,6 +169,11 @@ public class ExecutableFindOperationSupportTests {
assertThat(template.query(Person.class).matching(query(where("firstname").is("luke"))).one()).contains(luke);
}
@Test // DATAMONGO-2416
public void findByCriteria() {
assertThat(template.query(Person.class).matching(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();

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -39,6 +40,7 @@ import org.springframework.data.mongodb.core.query.Query;
* Unit tests for {@link ExecutableMapReduceOperationSupport}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @currentRead Beyond the Shadows - Brent Weeks
*/
@ExtendWith(MockitoExtension.class)
@@ -109,6 +111,18 @@ public class ExecutableMapReduceOperationSupportUnitTests {
isNull(), eq(Person.class));
}
@Test // DATAMONGO-2416
void usesCriteriaWhenPresent() {
when(template.getCollectionName(eq(Person.class))).thenReturn(STAR_WARS);
Query query = Query.query(where("lastname").is("skywalker"));
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION)
.matching(where("lastname").is("skywalker")).all();
verify(template).mapReduce(eq(query), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION),
isNull(), eq(Person.class));
}
@Test // DATAMONGO-1929
void usesProjectionWhenPresent() {

View File

@@ -84,6 +84,14 @@ public class ExecutableRemoveOperationSupportTests {
assertThat(result.getDeletedCount()).isEqualTo(1L);
}
@Test // DATAMONGO-2416
public void removeAllMatchingCriteria() {
DeleteResult result = template.remove(Person.class).matching(where("firstname").is("han")).all();
assertThat(result.getDeletedCount()).isEqualTo(1L);
}
@Test // DATAMONGO-1563
public void removeAllMatchingWithAlternateDomainTypeAndCollection() {

View File

@@ -120,6 +120,17 @@ public class ExecutableUpdateOperationSupportTests {
assertThat(result.getUpsertedId()).isNull();
}
@Test // DATAMONGO-2416
public void updateAllMatchingCriteria() {
UpdateResult result = template.update(Person.class).matching(where("id").is(han.getId()))
.apply(new Update().set("firstname", "Han"))
.all();
assertThat(result.getModifiedCount()).isEqualTo(1L);
assertThat(result.getUpsertedId()).isNull();
}
@Test // DATAMONGO-1563
public void updateWithDifferentDomainClassAndCollection() {

View File

@@ -163,6 +163,14 @@ public class ReactiveFindOperationSupportTests {
.verifyComplete();
}
@Test // DATAMONGO-2416
public void findAllByCriteria() {
template.query(Person.class).matching(where("firstname").is("luke")).all().as(StepVerifier::create) //
.expectNext(luke) //
.verifyComplete();
}
@Test // DATAMONGO-1719
public void findAllByWithCollectionUsingMappingInformation() {

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -39,6 +40,7 @@ import org.springframework.data.mongodb.core.query.Query;
* Unit tests for {@link ReactiveMapReduceOperationSupport}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @currentRead Beyond the Shadows - Brent Weeks
*/
@ExtendWith(MockitoExtension.class)
@@ -112,6 +114,19 @@ public class ReactiveMapReduceOperationSupportUnitTests {
eq(REDUCE_FUNCTION), isNull());
}
@Test // DATAMONGO-2416
void usesCriteriaWhenPresent() {
when(template.getCollectionName(eq(Person.class))).thenReturn(STAR_WARS);
Query query = Query.query(where("lastname").is("skywalker"));
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION)
.matching(where("lastname").is("skywalker")).all();
verify(template).mapReduce(eq(query), eq(Person.class), eq(STAR_WARS), eq(Person.class), eq(MAP_FUNCTION),
eq(REDUCE_FUNCTION), isNull());
}
@Test // DATAMONGO-1929
void usesProjectionWhenPresent() {

View File

@@ -85,6 +85,13 @@ public class ReactiveRemoveOperationSupportTests {
.consumeNextWith(actual -> assertThat(actual.getDeletedCount()).isEqualTo(1L)).verifyComplete();
}
@Test // DATAMONGO-1719
public void removeAllMatchingCriteria() {
template.remove(Person.class).matching(where("firstname").is("han")).all().as(StepVerifier::create)
.consumeNextWith(actual -> assertThat(actual.getDeletedCount()).isEqualTo(1L)).verifyComplete();
}
@Test // DATAMONGO-1719
public void removeAllMatchingWithAlternateDomainTypeAndCollection() {

View File

@@ -128,6 +128,17 @@ public class ReactiveUpdateOperationSupportTests {
}).verifyComplete();
}
@Test // DATAMONGO-2416
public void updateAllMatchingCriteria() {
template.update(Person.class).matching(where("id").is(han.getId())).apply(new Update().set("firstname", "Han"))
.all().as(StepVerifier::create).consumeNextWith(actual -> {
assertThat(actual.getModifiedCount()).isEqualTo(1L);
assertThat(actual.getUpsertedId()).isNull();
}).verifyComplete();
}
@Test // DATAMONGO-1719
public void updateWithDifferentDomainClassAndCollection() {

View File

@@ -100,7 +100,7 @@ class AbstractMongoQueryUnitTests {
doReturn(converter).when(mongoOperationsMock).getConverter();
doReturn(executableFind).when(mongoOperationsMock).query(any());
doReturn(withQueryMock).when(executableFind).as(any());
doReturn(withQueryMock).when(withQueryMock).matching(any());
doReturn(withQueryMock).when(withQueryMock).matching(any(Query.class));
when(mongoOperationsMock.remove(any(), any(), anyString())).thenReturn(deleteResultMock);
}

View File

@@ -52,7 +52,10 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* Unit tests for {@link AbstractReactiveMongoQuery}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @currentRead Way of Kings - Brandon Sanderson
*/
@ExtendWith(MockitoExtension.class)
@@ -81,7 +84,7 @@ class AbstractReactiveMongoQueryUnitTests {
doReturn(executableFind).when(mongoOperationsMock).query(any());
doReturn(withQueryMock).when(executableFind).as(any());
doReturn(withQueryMock).when(withQueryMock).matching(any());
doReturn(withQueryMock).when(withQueryMock).matching(any(Query.class));
}
@Test // DATAMONGO-1854