From 5ab75eb65aada8c3989b99b2441810e2e16204a0 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 21 May 2021 10:55:59 +0200 Subject: [PATCH] Polishing. Reduce dependencies in tests by using NoOpDbRefResolver. Add since tags. Tweak documentation. Extract entity references into own documentation fragment. Original pull request: #3647. Closes #3602. --- .../core/convert/MappingMongoConverter.java | 2 - .../MongoDatabaseFactoryReferenceLoader.java | 3 +- .../mongodb/core/convert/ReferenceLoader.java | 3 +- .../core/convert/ReferenceLookupDelegate.java | 1 + .../mongodb/core/mapping/DocumentPointer.java | 5 +- .../core/mapping/DocumentReference.java | 64 +-- .../MongoTransactionManagerUnitTests.java | 28 +- .../mongodb/core/CountQueryUnitTests.java | 33 +- .../MongoTemplateDocumentReferenceTests.java | 8 +- .../FilterExpressionUnitTests.java | 5 +- .../convert/CustomConvertersUnitTests.java | 3 +- .../convert/MappingMongoConverterTests.java | 26 +- .../convert/MongoExampleMapperUnitTests.java | 70 ++- .../core/convert/QueryMapperUnitTests.java | 22 +- .../core/convert/UpdateMapperUnitTests.java | 4 +- .../query/AbstractMongoQueryUnitTests.java | 5 +- .../ConvertingParameterAccessorUnitTests.java | 22 +- .../query/MongoQueryCreatorUnitTests.java | 120 ++--- .../query/PartTreeMongoQueryUnitTests.java | 38 +- .../src/test/resources/logback.xml | 1 - src/main/asciidoc/new-features.adoc | 2 +- .../reference/document-references.adoc | 440 ++++++++++++++++++ src/main/asciidoc/reference/mapping.adoc | 429 +---------------- 23 files changed, 680 insertions(+), 654 deletions(-) create mode 100644 src/main/asciidoc/reference/document-references.adoc diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 87f0adeb62..413ce2ce44 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -546,8 +546,6 @@ private void readAssociation(Association association, P DBRef dbref = value instanceof DBRef ? (DBRef) value : null; - // TODO: accessor.setProperty(property, dbRefResolver.resolveReference(property, value, referenceReader, - // context::convert)); accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler)); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java index 0973e5a5fb..d68af1fb5a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java @@ -29,8 +29,9 @@ /** * {@link ReferenceLoader} implementation using a {@link MongoDatabaseFactory} to obtain raw {@link Document documents} * for linked entities via a {@link ReferenceLoader.DocumentReferenceQuery}. - * + * * @author Christoph Strobl + * @since 3.3 */ public class MongoDatabaseFactoryReferenceLoader implements ReferenceLoader { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java index 2f96f57da2..70a0f43c0f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java @@ -28,7 +28,7 @@ /** * The {@link ReferenceLoader} obtains raw {@link Document documents} for linked entities via a * {@link ReferenceLoader.DocumentReferenceQuery}. - * + * * @author Christoph Strobl * @since 3.3 */ @@ -79,7 +79,6 @@ default Bson getSort() { return new Document(); } - // TODO: Move apply method into something else that holds the collection and knows about single item/multi-item default Iterable apply(MongoCollection collection) { return restoreOrder(collection.find(getQuery()).sort(getSort())); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java index 09f4c1a8ae..616abb325e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java @@ -57,6 +57,7 @@ * * @author Christoph Strobl * @author Mark Paluch + * @since 3.3 */ public final class ReferenceLookupDelegate { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentPointer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentPointer.java index de7fbff866..3b432a8c12 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentPointer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentPointer.java @@ -17,8 +17,9 @@ /** * A custom pointer to a linked document to be used along with {@link DocumentReference} for storing the linkage value. - * + * * @author Christoph Strobl + * @since 3.3 */ @FunctionalInterface public interface DocumentPointer { @@ -27,7 +28,7 @@ public interface DocumentPointer { * The actual pointer value. This can be any simple type, like a {@link String} or {@link org.bson.types.ObjectId} or * a {@link org.bson.Document} holding more information like the target collection, multiple fields forming the key, * etc. - * + * * @return the value stored in MongoDB and used for constructing the {@link DocumentReference#lookup() lookup query}. */ T getPointer(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentReference.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentReference.java index 0846c4022c..6fd5e96877 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentReference.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/DocumentReference.java @@ -22,13 +22,14 @@ import java.lang.annotation.Target; import org.springframework.data.annotation.Reference; +import org.springframework.data.mongodb.MongoDatabaseFactory; /** - * A {@link DocumentReference} offers an alternative way of linking entities in MongoDB. While the goal is the same as - * when using {@link DBRef}, the store representation is different and can be literally anything, a single value, an - * entire {@link org.bson.Document}, basically everything that can be stored in MongoDB. By default, the mapping layer - * will use the referenced entities {@literal id} value for storage and retrieval. - * + * A {@link DocumentReference} allows referencing entities in MongoDB using a flexible schema. While the goal is the + * same as when using {@link DBRef}, the store representation is different. The reference can be anything, a single + * value, an entire {@link org.bson.Document}, basically everything that can be stored in MongoDB. By default, the + * mapping layer will use the referenced entities {@literal id} value for storage and retrieval. + * *
  * public class Account {
  *   private String id;
@@ -40,7 +41,7 @@
  *   @DocumentReference
  *   private List<Account> accounts;
  * }
- * 
+ *
  * Account account = ...
  *
  * mongoTemplate.insert(account);
@@ -50,43 +51,41 @@
  *   .apply(new Update().push("accounts").value(account))
  *   .first();
  * 
- * - * {@link #lookup()} allows to define custom queries that are independent from the {@literal id} field and in - * combination with {@link org.springframework.data.convert.WritingConverter writing converters} offer a flexible way of - * defining links between entities. - * + * + * {@link #lookup()} allows defining a query filter that is independent from the {@literal _id} field and in combination + * with {@link org.springframework.data.convert.WritingConverter writing converters} offers a flexible way of defining + * references between entities. + * *
  * public class Book {
- * 	 private ObjectId id;
- * 	 private String title;
+ * 	private ObjectId id;
+ * 	private String title;
  *
- * 	 @Field("publisher_ac")
- * 	 @DocumentReference(lookup = "{ 'acronym' : ?#{#target} }")
- * 	 private Publisher publisher;
+ * 	@Field("publisher_ac") @DocumentReference(lookup = "{ 'acronym' : ?#{#target} }") private Publisher publisher;
  * }
  *
  * public class Publisher {
  *
- * 	 private ObjectId id;
- * 	 private String acronym;
- * 	 private String name;
+ * 	private ObjectId id;
+ * 	private String acronym;
+ * 	private String name;
  *
- * 	 @DocumentReference(lazy = true)
- * 	 private List<Book> books;
+ * 	@DocumentReference(lazy = true) private List<Book> books;
  * }
  *
  * @WritingConverter
  * public class PublisherReferenceConverter implements Converter<Publisher, DocumentPointer<String>> {
  *
- *    public DocumentPointer<String> convert(Publisher source) {
+ * 	public DocumentPointer<String> convert(Publisher source) {
  * 		return () -> source.getAcronym();
- *    }
+ * 	}
  * }
  * 
* * @author Christoph Strobl * @since 3.3 - * @see MongoDB Reference Documentation + * @see MongoDB + * Reference Documentation */ @Documented @Retention(RetentionPolicy.RUNTIME) @@ -95,22 +94,25 @@ public @interface DocumentReference { /** - * The database the linked entity resides in. + * The database the referenced entity resides in. Uses the default database provided by + * {@link org.springframework.data.mongodb.MongoDatabaseFactory} if empty. * - * @return empty String by default. Uses the default database provided buy the {@link org.springframework.data.mongodb.MongoDatabaseFactory}. + * @see MongoDatabaseFactory#getMongoDatabase() + * @see MongoDatabaseFactory#getMongoDatabase(String) */ String db() default ""; /** - * The database the linked entity resides in. + * The collection the referenced entity resides in. Defaults to the collection of the referenced entity type. * - * @return empty String by default. Uses the property type for collection resolution. + * @see MongoPersistentEntity#getCollection() */ String collection() default ""; /** - * The single document lookup query. In case of an {@link java.util.Collection} or {@link java.util.Map} property - * the individual lookups are combined via an `$or` operator. + * The single document lookup query. In case of an {@link java.util.Collection} or {@link java.util.Map} property the + * individual lookups are combined via an {@code $or} operator. {@code target} points to the source value (or + * document) stored at the reference property. Properties of {@code target} can be used to define the reference query. * * @return an {@literal _id} based lookup. */ @@ -118,8 +120,6 @@ /** * A specific sort. - * - * @return empty String by default. */ String sort() default ""; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoTransactionManagerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoTransactionManagerUnitTests.java index dfb48fdbb1..bb05a283b2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoTransactionManagerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoTransactionManagerUnitTests.java @@ -25,6 +25,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.mongodb.core.MongoExceptionTranslator; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; @@ -37,12 +38,15 @@ import com.mongodb.client.ClientSession; import com.mongodb.client.MongoDatabase; import com.mongodb.session.ServerSession; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; /** * @author Christoph Strobl */ @ExtendWith(MockitoExtension.class) -public class MongoTransactionManagerUnitTests { +@MockitoSettings(strictness = Strictness.LENIENT) +class MongoTransactionManagerUnitTests { @Mock ClientSession session; @Mock ClientSession session2; @@ -53,23 +57,25 @@ public class MongoTransactionManagerUnitTests { @Mock MongoDatabase db2; @BeforeEach - public void setUp() { + void setUp() { when(dbFactory.getSession(any())).thenReturn(session, session2); + when(dbFactory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); + when(dbFactory2.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); when(dbFactory.withSession(session)).thenReturn(dbFactory); when(dbFactory.getMongoDatabase()).thenReturn(db); when(session.getServerSession()).thenReturn(serverSession); } @AfterEach - public void verifyTransactionSynchronizationManager() { + void verifyTransactionSynchronizationManager() { assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty()).isTrue(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); } @Test // DATAMONGO-1920 - public void triggerCommitCorrectly() { + void triggerCommitCorrectly() { MongoTransactionManager txManager = new MongoTransactionManager(dbFactory); TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition()); @@ -91,7 +97,7 @@ public void triggerCommitCorrectly() { } @Test // DATAMONGO-1920 - public void participateInOnGoingTransactionWithCommit() { + void participateInOnGoingTransactionWithCommit() { MongoTransactionManager txManager = new MongoTransactionManager(dbFactory); TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition()); @@ -126,7 +132,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Test // DATAMONGO-1920 - public void participateInOnGoingTransactionWithRollbackOnly() { + void participateInOnGoingTransactionWithRollbackOnly() { MongoTransactionManager txManager = new MongoTransactionManager(dbFactory); TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition()); @@ -163,7 +169,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Test // DATAMONGO-1920 - public void triggerRollbackCorrectly() { + void triggerRollbackCorrectly() { MongoTransactionManager txManager = new MongoTransactionManager(dbFactory); TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition()); @@ -185,7 +191,7 @@ public void triggerRollbackCorrectly() { } @Test // DATAMONGO-1920 - public void suspendTransactionWhilePropagationNotSupported() { + void suspendTransactionWhilePropagationNotSupported() { MongoTransactionManager txManager = new MongoTransactionManager(dbFactory); TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition()); @@ -228,7 +234,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Test // DATAMONGO-1920 - public void suspendTransactionWhilePropagationRequiresNew() { + void suspendTransactionWhilePropagationRequiresNew() { when(dbFactory.withSession(session2)).thenReturn(dbFactory2); when(dbFactory2.getMongoDatabase()).thenReturn(db2); @@ -277,7 +283,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Test // DATAMONGO-1920 - public void readonlyShouldInitiateASessionStartAndCommitTransaction() { + void readonlyShouldInitiateASessionStartAndCommitTransaction() { MongoTransactionManager txManager = new MongoTransactionManager(dbFactory); @@ -303,7 +309,7 @@ public void readonlyShouldInitiateASessionStartAndCommitTransaction() { } @Test // DATAMONGO-1920 - public void readonlyShouldInitiateASessionStartAndRollbackTransaction() { + void readonlyShouldInitiateASessionStartAndRollbackTransaction() { MongoTransactionManager txManager = new MongoTransactionManager(dbFactory); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java index 4b5ed6f2a8..dcb35a7d4b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java @@ -27,6 +27,7 @@ import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; @@ -39,27 +40,25 @@ * @author Mark Paluch * @author Christoph Strobl */ -public class CountQueryUnitTests { +class CountQueryUnitTests { - QueryMapper mapper; - MongoMappingContext context; - MappingMongoConverter converter; - - MongoDatabaseFactory factory = mock(MongoDatabaseFactory.class); + private QueryMapper mapper; + private MongoMappingContext context; + private MappingMongoConverter converter; @BeforeEach - public void setUp() { + void setUp() { this.context = new MongoMappingContext(); - this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); this.converter.afterPropertiesSet(); this.mapper = new QueryMapper(converter); } @Test // DATAMONGO-2059 - public void nearToGeoWithinWithoutDistance() { + void nearToGeoWithinWithoutDistance() { Query source = query(where("location").near(new Point(-73.99171, 40.738868))); org.bson.Document target = postProcessQueryForCount(source); @@ -69,7 +68,7 @@ public void nearToGeoWithinWithoutDistance() { } @Test // DATAMONGO-2059 - public void nearAndExisting$and() { + void nearAndExisting$and() { Query source = query(where("location").near(new Point(-73.99171, 40.738868)).minDistance(0.01)) .addCriteria(new Criteria().andOperator(where("foo").is("bar"))); @@ -83,7 +82,7 @@ public void nearToGeoWithinWithoutDistance() { } @Test // DATAMONGO-2059 - public void nearSphereToGeoWithinWithoutDistance() { + void nearSphereToGeoWithinWithoutDistance() { Query source = query(where("location").nearSphere(new Point(-73.99171, 40.738868))); org.bson.Document target = postProcessQueryForCount(source); @@ -93,7 +92,7 @@ public void nearSphereToGeoWithinWithoutDistance() { } @Test // DATAMONGO-2059 - public void nearToGeoWithinWithMaxDistance() { + void nearToGeoWithinWithMaxDistance() { Query source = query(where("location").near(new Point(-73.99171, 40.738868)).maxDistance(10)); org.bson.Document target = postProcessQueryForCount(source); @@ -103,7 +102,7 @@ public void nearToGeoWithinWithMaxDistance() { } @Test // DATAMONGO-2059 - public void nearSphereToGeoWithinWithMaxDistance() { + void nearSphereToGeoWithinWithMaxDistance() { Query source = query(where("location").nearSphere(new Point(-73.99171, 40.738868)).maxDistance(10)); org.bson.Document target = postProcessQueryForCount(source); @@ -113,7 +112,7 @@ public void nearSphereToGeoWithinWithMaxDistance() { } @Test // DATAMONGO-2059 - public void nearToGeoWithinWithMinDistance() { + void nearToGeoWithinWithMinDistance() { Query source = query(where("location").near(new Point(-73.99171, 40.738868)).minDistance(0.01)); org.bson.Document target = postProcessQueryForCount(source); @@ -124,7 +123,7 @@ public void nearToGeoWithinWithMinDistance() { } @Test // DATAMONGO-2059 - public void nearToGeoWithinWithMaxDistanceAndCombinedWithOtherCriteria() { + void nearToGeoWithinWithMaxDistanceAndCombinedWithOtherCriteria() { Query source = query( where("name").is("food").and("location").near(new Point(-73.99171, 40.738868)).maxDistance(10)); @@ -135,7 +134,7 @@ public void nearToGeoWithinWithMaxDistanceAndCombinedWithOtherCriteria() { } @Test // DATAMONGO-2059 - public void nearToGeoWithinWithMinDistanceOrCombinedWithOtherCriteria() { + void nearToGeoWithinWithMinDistanceOrCombinedWithOtherCriteria() { Query source = query(new Criteria().orOperator(where("name").is("food"), where("location").near(new Point(-73.99171, 40.738868)).minDistance(0.01))); @@ -146,7 +145,7 @@ public void nearToGeoWithinWithMinDistanceOrCombinedWithOtherCriteria() { } @Test // DATAMONGO-2059 - public void nearToGeoWithinWithMaxDistanceOrCombinedWithOtherCriteria() { + void nearToGeoWithinWithMaxDistanceOrCombinedWithOtherCriteria() { Query source = query(new Criteria().orOperator(where("name").is("food"), where("location").near(new Point(-73.99171, 40.738868)).maxDistance(10))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDocumentReferenceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDocumentReferenceTests.java index 2c1caf316a..fa1deb4f1c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDocumentReferenceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDocumentReferenceTests.java @@ -1094,17 +1094,17 @@ public Object toReference() { } } - static class ReferencableConverter implements Converter { + static class ReferencableConverter implements Converter> { @Nullable @Override - public DocumentPointer convert(ReferenceAble source) { + public DocumentPointer convert(ReferenceAble source) { return source::toReference; } } @WritingConverter - class DocumentToSimpleObjectRefWithReadingConverter + static class DocumentToSimpleObjectRefWithReadingConverter implements Converter, SimpleObjectRefWithReadingConverter> { @Nullable @@ -1118,7 +1118,7 @@ public SimpleObjectRefWithReadingConverter convert(DocumentPointer sou } @WritingConverter - class SimpleObjectRefWithReadingConverterToDocumentConverter + static class SimpleObjectRefWithReadingConverterToDocumentConverter implements Converter> { @Nullable diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java index 1580b4efb0..a318a5559b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java @@ -32,6 +32,7 @@ import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -41,8 +42,6 @@ @ExtendWith(MockitoExtension.class) class FilterExpressionUnitTests { - @Mock MongoDatabaseFactory mongoDbFactory; - private AggregationOperationContext aggregationContext; private MongoMappingContext mappingContext; @@ -51,7 +50,7 @@ void setUp() { mappingContext = new MongoMappingContext(); aggregationContext = new TypeBasedAggregationOperationContext(Sales.class, mappingContext, - new QueryMapper(new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), mappingContext))); + new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext))); } @Test // DATAMONGO-1491 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java index 1c1ba0715d..67b51f1140 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java @@ -49,7 +49,6 @@ class CustomConvertersUnitTests { @Mock BarToDocumentConverter barToDocumentConverter; @Mock DocumentToBarConverter documentToBarConverter; - @Mock MongoDatabaseFactory mongoDbFactory; private MongoMappingContext context; @@ -67,7 +66,7 @@ void setUp() { context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); context.initialize(); - converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), context); + converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); converter.setCustomConversions(conversions); converter.afterPropertiesSet(); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java index 66c2cc9822..2b17ed4b06 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java @@ -59,18 +59,18 @@ @ExtendWith(MongoClientExtension.class) public class MappingMongoConverterTests { - public static final String DATABASE = "mapping-converter-tests"; + private static final String DATABASE = "mapping-converter-tests"; - static @Client MongoClient client; + private static @Client MongoClient client; - MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(client, DATABASE); + private MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(client, DATABASE); - MappingMongoConverter converter; - MongoMappingContext mappingContext; - DbRefResolver dbRefResolver; + private MappingMongoConverter converter; + private MongoMappingContext mappingContext; + private DbRefResolver dbRefResolver; @BeforeEach - public void setUp() { + void setUp() { MongoDatabase database = client.getDatabase(DATABASE); @@ -90,7 +90,7 @@ public void setUp() { } @Test // DATAMONGO-2004 - public void resolvesLazyDBRefOnAccess() { + void resolvesLazyDBRefOnAccess() { client.getDatabase(DATABASE).getCollection("samples") .insertMany(Arrays.asList(new Document("_id", "sample-1").append("value", "one"), @@ -102,7 +102,6 @@ public void resolvesLazyDBRefOnAccess() { WithLazyDBRef target = converter.read(WithLazyDBRef.class, source); verify(dbRefResolver).resolveDbRef(any(), isNull(), any(), any()); - verifyNoMoreInteractions(dbRefResolver); assertThat(target.lazyList).isInstanceOf(LazyLoadingProxy.class); assertThat(target.getLazyList()).contains(new Sample("sample-1", "one"), new Sample("sample-2", "two")); @@ -111,7 +110,7 @@ public void resolvesLazyDBRefOnAccess() { } @Test // DATAMONGO-2004 - public void resolvesLazyDBRefConstructorArgOnAccess() { + void resolvesLazyDBRefConstructorArgOnAccess() { client.getDatabase(DATABASE).getCollection("samples") .insertMany(Arrays.asList(new Document("_id", "sample-1").append("value", "one"), @@ -123,7 +122,6 @@ public void resolvesLazyDBRefConstructorArgOnAccess() { WithLazyDBRefAsConstructorArg target = converter.read(WithLazyDBRefAsConstructorArg.class, source); verify(dbRefResolver).resolveDbRef(any(), isNull(), any(), any()); - verifyNoMoreInteractions(dbRefResolver); assertThat(target.lazyList).isInstanceOf(LazyLoadingProxy.class); assertThat(target.getLazyList()).contains(new Sample("sample-1", "one"), new Sample("sample-2", "two")); @@ -132,7 +130,7 @@ public void resolvesLazyDBRefConstructorArgOnAccess() { } @Test // DATAMONGO-2400 - public void readJavaTimeValuesWrittenViaCodec() { + void readJavaTimeValuesWrittenViaCodec() { configureConverterWithNativeJavaTimeCodec(); MongoCollection mongoCollection = client.getDatabase(DATABASE).getCollection("java-time-types"); @@ -160,7 +158,7 @@ public static class WithLazyDBRef { @Id String id; @DBRef(lazy = true) List lazyList; - public List getLazyList() { + List getLazyList() { return lazyList; } } @@ -176,7 +174,7 @@ public WithLazyDBRefAsConstructorArg(String id, List lazyList) { this.lazyList = lazyList; } - public List getLazyList() { + List getLazyList() { return lazyList; } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java index ef92b8ff0c..796eecc7f7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java @@ -54,27 +54,25 @@ * @author Mark Paluch */ @ExtendWith(MockitoExtension.class) -public class MongoExampleMapperUnitTests { +class MongoExampleMapperUnitTests { - MongoExampleMapper mapper; - MongoMappingContext context; - MappingMongoConverter converter; - - @Mock MongoDatabaseFactory factory; + private MongoExampleMapper mapper; + private MongoMappingContext context; + private MappingMongoConverter converter; @BeforeEach - public void setUp() { + void setUp() { this.context = new MongoMappingContext(); - this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); this.converter.afterPropertiesSet(); this.mapper = new MongoExampleMapper(converter); } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { FlatDocument probe = new FlatDocument(); probe.id = "steelheart"; @@ -84,7 +82,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { FlatDocument probe = new FlatDocument(); probe.id = "steelheart"; @@ -98,7 +96,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -110,7 +108,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { FlatDocument probe = new FlatDocument(); probe.listOfString = Arrays.asList("Prof", "Tia", "David"); @@ -122,7 +120,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() { FlatDocument probe = new FlatDocument(); probe.customNamedField = "Mitosis"; @@ -132,7 +130,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() } @Test // DATAMONGO-1245 - public void typedExampleShouldContainTypeRestriction() { + void typedExampleShouldContainTypeRestriction() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); @@ -146,7 +144,7 @@ public void typedExampleShouldContainTypeRestriction() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() { + void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); @@ -157,7 +155,7 @@ public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatc } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() { + void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); @@ -170,7 +168,7 @@ public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictM } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -184,7 +182,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarti } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() { + void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() { FlatDocument probe = new FlatDocument(); probe.stringValue = "fire.ight"; @@ -198,7 +196,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMat } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -212,7 +210,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -226,7 +224,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -240,7 +238,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMat } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { + void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -255,7 +253,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedWhenContainingDBRef() { + void exampleShouldBeMappedWhenContainingDBRef() { FlatDocument probe = new FlatDocument(); probe.stringValue = "steelheart"; @@ -271,7 +269,7 @@ public void exampleShouldBeMappedWhenContainingDBRef() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedWhenDBRefIsNull() { + void exampleShouldBeMappedWhenDBRefIsNull() { FlatDocument probe = new FlatDocument(); probe.stringValue = "steelheart"; @@ -283,7 +281,7 @@ public void exampleShouldBeMappedWhenDBRefIsNull() { } @Test // DATAMONGO-1245 - public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { + void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { ClassWithGeoTypes probe = new ClassWithGeoTypes(); probe.legacyPoint = new Point(10D, 20D); @@ -296,7 +294,7 @@ public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { } @Test // DATAMONGO-1245 - public void mappingShouldExcludeFieldWithCustomNameCorrectly() { + void mappingShouldExcludeFieldWithCustomNameCorrectly() { FlatDocument probe = new FlatDocument(); probe.customNamedField = "foo"; @@ -311,7 +309,7 @@ public void mappingShouldExcludeFieldWithCustomNameCorrectly() { } @Test // DATAMONGO-1245 - public void mappingShouldExcludeFieldCorrectly() { + void mappingShouldExcludeFieldCorrectly() { FlatDocument probe = new FlatDocument(); probe.customNamedField = "foo"; @@ -326,7 +324,7 @@ public void mappingShouldExcludeFieldCorrectly() { } @Test // DATAMONGO-1245 - public void mappingShouldExcludeNestedFieldCorrectly() { + void mappingShouldExcludeNestedFieldCorrectly() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); @@ -342,7 +340,7 @@ public void mappingShouldExcludeNestedFieldCorrectly() { } @Test // DATAMONGO-1245 - public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { + void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); @@ -358,7 +356,7 @@ public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { } @Test // DATAMONGO-1245 - public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() { + void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -372,7 +370,7 @@ public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMa } @Test // DATAMONGO-1245 - public void mappingShouldIncludePropertiesFromHierarchicalDocument() { + void mappingShouldIncludePropertiesFromHierarchicalDocument() { HierachicalDocument probe = new HierachicalDocument(); probe.stringValue = "firefight"; @@ -386,7 +384,7 @@ public void mappingShouldIncludePropertiesFromHierarchicalDocument() { } @Test // DATAMONGO-1459 - public void mapsAnyMatchingExampleCorrectly() { + void mapsAnyMatchingExampleCorrectly() { FlatDocument probe = new FlatDocument(); probe.stringValue = "firefight"; @@ -398,7 +396,7 @@ public void mapsAnyMatchingExampleCorrectly() { } @Test // DATAMONGO-1768 - public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPath() { + void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPath() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); @@ -411,13 +409,13 @@ public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPath() { } @Test // DATAMONGO-1768 - public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPathWhenUsingCustomTypeMapper() { + void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPathWhenUsingCustomTypeMapper() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); probe.flatDoc.stringValue = "conflux"; - MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper() { @Override @@ -445,7 +443,7 @@ public void writeType(TypeInformation info, Bson sink) { } @Test // DATAMONGO-1768 - public void untypedExampleShouldNotInferTypeRestriction() { + void untypedExampleShouldNotInferTypeRestriction() { WrapperDocument probe = new WrapperDocument(); probe.flatDoc = new FlatDocument(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index d371b32c12..f7b5ec76d7 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -34,14 +34,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.geo.Point; -import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; @@ -80,14 +79,12 @@ public class QueryMapperUnitTests { private MongoMappingContext context; private MappingMongoConverter converter; - @Mock MongoDatabaseFactory factory; - @BeforeEach void beforeEach() { this.context = new MongoMappingContext(); - this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); this.converter.afterPropertiesSet(); this.mapper = new QueryMapper(converter); @@ -1502,19 +1499,4 @@ static class WithDocumentReferences { } - // TODO - @Test - void xxx() { - - Sample sample = new Sample(); - sample.foo = "sample-id"; - - Query query = query(where("sample").is(sample)); - - org.bson.Document mappedObject = mapper.getMappedObject(query.getQueryObject(), - context.getPersistentEntity(WithDocumentReferences.class)); - - System.out.println("mappedObject.toJson(): " + mappedObject.toJson()); - } - } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index f5b5493327..a8d5f12b9f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -48,6 +48,7 @@ import org.springframework.data.mapping.MappingException; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.DocumentTestUtils; +import org.springframework.data.mongodb.core.MongoExceptionTranslator; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.Unwrapped; @@ -70,7 +71,6 @@ @ExtendWith(MockitoExtension.class) class UpdateMapperUnitTests { - @Mock MongoDatabaseFactory factory; private MappingMongoConverter converter; private MongoMappingContext context; private UpdateMapper mapper; @@ -88,7 +88,7 @@ void setUp() { this.context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); this.context.initialize(); - this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); this.converter.setCustomConversions(conversions); this.converter.afterPropertiesSet(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index c2803b6124..92c99185eb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -45,6 +45,7 @@ import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind; import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; +import org.springframework.data.mongodb.core.MongoExceptionTranslator; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.convert.DbRefResolver; @@ -93,7 +94,9 @@ void setUp() { doReturn(persitentEntityMock).when(mappingContextMock).getRequiredPersistentEntity(Mockito.any(Class.class)); doReturn(Person.class).when(persitentEntityMock).getType(); - DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDatabaseFactory.class)); + MongoDatabaseFactory mongoDbFactory = mock(MongoDatabaseFactory.class); + when(mongoDbFactory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); + DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory); MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContextMock); converter.afterPropertiesSet(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java index 87994bcbec..1624f40d77 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java @@ -28,6 +28,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.core.MongoExceptionTranslator; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -46,35 +47,36 @@ * @author Christoph Strobl */ @ExtendWith(MockitoExtension.class) -public class ConvertingParameterAccessorUnitTests { +class ConvertingParameterAccessorUnitTests { @Mock MongoDatabaseFactory factory; @Mock MongoParameterAccessor accessor; - MongoMappingContext context; - MappingMongoConverter converter; - DbRefResolver resolver; + private MongoMappingContext context; + private MappingMongoConverter converter; + private DbRefResolver resolver; @BeforeEach - public void setUp() { + void setUp() { + when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); this.context = new MongoMappingContext(); this.resolver = new DefaultDbRefResolver(factory); this.converter = new MappingMongoConverter(resolver, context); } @Test - public void rejectsNullDbRefResolver() { + void rejectsNullDbRefResolver() { assertThatIllegalArgumentException().isThrownBy(() -> new MappingMongoConverter((DbRefResolver) null, context)); } @Test - public void rejectsNullContext() { + void rejectsNullContext() { assertThatIllegalArgumentException().isThrownBy(() -> new MappingMongoConverter(resolver, null)); } @Test - public void convertsCollectionUponAccess() { + void convertsCollectionUponAccess() { when(accessor.getBindableValue(0)).thenReturn(Arrays.asList("Foo")); @@ -88,7 +90,7 @@ public void convertsCollectionUponAccess() { } @Test // DATAMONGO-505 - public void convertsAssociationsToDBRef() { + void convertsAssociationsToDBRef() { Property property = new Property(); property.id = 5L; @@ -102,7 +104,7 @@ public void convertsAssociationsToDBRef() { } @Test // DATAMONGO-505 - public void convertsAssociationsToDBRefForCollections() { + void convertsAssociationsToDBRefForCollections() { Property property = new Property(); property.id = 5L; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index 12446f7e0f..02d4b7bc09 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -38,12 +38,14 @@ import org.springframework.data.geo.Shape; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.core.MongoExceptionTranslator; import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.geo.GeoJsonLineString; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; @@ -67,22 +69,20 @@ * @author Thomas Darimont * @author Christoph Strobl */ -public class MongoQueryCreatorUnitTests { +class MongoQueryCreatorUnitTests { - MappingContext, MongoPersistentProperty> context; - MongoConverter converter; + private MappingContext, MongoPersistentProperty> context; + private MongoConverter converter; @BeforeEach - public void beforeEach() { + void beforeEach() { context = new MongoMappingContext(); - - DbRefResolver resolver = new DefaultDbRefResolver(mock(MongoDatabaseFactory.class)); - converter = new MappingMongoConverter(resolver, context); + converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context); } @Test - public void createsQueryCorrectly() { + void createsQueryCorrectly() { PartTree tree = new PartTree("findByFirstName", Person.class); @@ -92,7 +92,7 @@ public void createsQueryCorrectly() { } @Test // DATAMONGO-469 - public void createsAndQueryCorrectly() { + void createsAndQueryCorrectly() { Person person = new Person(); MongoQueryCreator creator = new MongoQueryCreator(new PartTree("findByFirstNameAndFriend", Person.class), @@ -103,7 +103,7 @@ public void createsAndQueryCorrectly() { } @Test - public void createsNotNullQueryCorrectly() { + void createsNotNullQueryCorrectly() { PartTree tree = new PartTree("findByFirstNameNotNull", Person.class); Query query = new MongoQueryCreator(tree, getAccessor(converter), context).createQuery(); @@ -112,7 +112,7 @@ public void createsNotNullQueryCorrectly() { } @Test - public void createsIsNullQueryCorrectly() { + void createsIsNullQueryCorrectly() { PartTree tree = new PartTree("findByFirstNameIsNull", Person.class); Query query = new MongoQueryCreator(tree, getAccessor(converter), context).createQuery(); @@ -121,7 +121,7 @@ public void createsIsNullQueryCorrectly() { } @Test - public void bindsMetricDistanceParameterToNearSphereCorrectly() throws Exception { + void bindsMetricDistanceParameterToNearSphereCorrectly() throws Exception { Point point = new Point(10, 20); Distance distance = new Distance(2.5, Metrics.KILOMETERS); @@ -132,7 +132,7 @@ public void bindsMetricDistanceParameterToNearSphereCorrectly() throws Exception } @Test - public void bindsDistanceParameterToNearCorrectly() throws Exception { + void bindsDistanceParameterToNearCorrectly() throws Exception { Point point = new Point(10, 20); Distance distance = new Distance(2.5); @@ -143,7 +143,7 @@ public void bindsDistanceParameterToNearCorrectly() throws Exception { } @Test - public void createsLessThanEqualQueryCorrectly() { + void createsLessThanEqualQueryCorrectly() { PartTree tree = new PartTree("findByAgeLessThanEqual", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, 18), context); @@ -153,7 +153,7 @@ public void createsLessThanEqualQueryCorrectly() { } @Test - public void createsGreaterThanEqualQueryCorrectly() { + void createsGreaterThanEqualQueryCorrectly() { PartTree tree = new PartTree("findByAgeGreaterThanEqual", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, 18), context); @@ -163,7 +163,7 @@ public void createsGreaterThanEqualQueryCorrectly() { } @Test // DATAMONGO-338 - public void createsExistsClauseCorrectly() { + void createsExistsClauseCorrectly() { PartTree tree = new PartTree("findByAgeExists", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, true), context); @@ -172,7 +172,7 @@ public void createsExistsClauseCorrectly() { } @Test // DATAMONGO-338 - public void createsRegexClauseCorrectly() { + void createsRegexClauseCorrectly() { PartTree tree = new PartTree("findByFirstNameRegex", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, ".*"), context); @@ -181,7 +181,7 @@ public void createsRegexClauseCorrectly() { } @Test // DATAMONGO-338 - public void createsTrueClauseCorrectly() { + void createsTrueClauseCorrectly() { PartTree tree = new PartTree("findByActiveTrue", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context); @@ -190,7 +190,7 @@ public void createsTrueClauseCorrectly() { } @Test // DATAMONGO-338 - public void createsFalseClauseCorrectly() { + void createsFalseClauseCorrectly() { PartTree tree = new PartTree("findByActiveFalse", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context); @@ -199,7 +199,7 @@ public void createsFalseClauseCorrectly() { } @Test // DATAMONGO-413 - public void createsOrQueryCorrectly() { + void createsOrQueryCorrectly() { PartTree tree = new PartTree("findByFirstNameOrAge", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "Dave", 42), context); @@ -209,7 +209,7 @@ public void createsOrQueryCorrectly() { } @Test // DATAMONGO-347 - public void createsQueryReferencingADBRefCorrectly() { + void createsQueryReferencingADBRefCorrectly() { User user = new User(); user.id = new ObjectId(); @@ -222,7 +222,7 @@ public void createsQueryReferencingADBRefCorrectly() { } @Test // DATAMONGO-418 - public void createsQueryWithStartingWithPredicateCorrectly() { + void createsQueryWithStartingWithPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameStartingWith", User.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "Matt"), context); @@ -232,7 +232,7 @@ public void createsQueryWithStartingWithPredicateCorrectly() { } @Test // DATAMONGO-418 - public void createsQueryWithEndingWithPredicateCorrectly() { + void createsQueryWithEndingWithPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameEndingWith", User.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "ews"), context); @@ -242,7 +242,7 @@ public void createsQueryWithEndingWithPredicateCorrectly() { } @Test // DATAMONGO-418 - public void createsQueryWithContainingPredicateCorrectly() { + void createsQueryWithContainingPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameContaining", User.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "thew"), context); @@ -268,7 +268,7 @@ private void assertBindsDistanceToQuery(Point point, Distance distance, Query re } @Test // DATAMONGO-770 - public void createsQueryWithFindByIgnoreCaseCorrectly() { + void createsQueryWithFindByIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByfirstNameIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context); @@ -278,7 +278,7 @@ public void createsQueryWithFindByIgnoreCaseCorrectly() { } @Test // DATAMONGO-770 - public void createsQueryWithFindByNotIgnoreCaseCorrectly() { + void createsQueryWithFindByNotIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameNotIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context); @@ -288,7 +288,7 @@ public void createsQueryWithFindByNotIgnoreCaseCorrectly() { } @Test // DATAMONGO-770 - public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() { + void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameStartingWithIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context); @@ -298,7 +298,7 @@ public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() { } @Test // DATAMONGO-770 - public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() { + void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameEndingWithIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context); @@ -308,7 +308,7 @@ public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() { } @Test // DATAMONGO-770 - public void createsQueryWithFindByContainingIgnoreCaseCorrectly() { + void createsQueryWithFindByContainingIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameContainingIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context); @@ -318,7 +318,7 @@ public void createsQueryWithFindByContainingIgnoreCaseCorrectly() { } @Test // DATAMONGO-770 - public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty() { + void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty() { PartTree tree = new PartTree("findByFirstNameAndAgeIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "foo", 42), context); @@ -328,7 +328,7 @@ public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty( } @Test // DATAMONGO-770 - public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase() { + void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase() { PartTree tree = new PartTree("findByFirstNameAndAgeAllIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave", 42), context); @@ -338,7 +338,7 @@ public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase( } @Test // DATAMONGO-566 - public void shouldCreateDeleteByQueryCorrectly() { + void shouldCreateDeleteByQueryCorrectly() { PartTree tree = new PartTree("deleteByFirstName", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave", 42), context); @@ -350,7 +350,7 @@ public void shouldCreateDeleteByQueryCorrectly() { } @Test // DATAMONGO-566 - public void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressions() { + void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressions() { PartTree tree = new PartTree("deleteByFirstNameAndAgeAllIgnoreCase", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave", 42), context); @@ -362,7 +362,7 @@ public void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressi } @Test // DATAMONGO-1075 - public void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() { + void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() { PartTree tree = new PartTree("findByEmailAddressesContaining", User.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context); @@ -373,7 +373,7 @@ public void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() { } @Test // DATAMONGO-1075 - public void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() { + void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() { PartTree tree = new PartTree("findByEmailAddressesNotContaining", User.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context); @@ -384,7 +384,7 @@ public void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() { } @Test // DATAMONGO-1075, DATAMONGO-1425 - public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { + void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { PartTree tree = new PartTree("findByUsernameNotContaining", User.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "thew"), context); @@ -395,7 +395,7 @@ public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { } @Test // DATAMONGO-1139 - public void createsNonSphericalNearForDistanceWithDefaultMetric() { + void createsNonSphericalNearForDistanceWithDefaultMetric() { Point point = new Point(1.0, 1.0); Distance distance = new Distance(1.0); @@ -408,7 +408,7 @@ public void createsNonSphericalNearForDistanceWithDefaultMetric() { } @Test // DATAMONGO-1136 - public void shouldCreateWithinQueryCorrectly() { + void shouldCreateWithinQueryCorrectly() { Point first = new Point(1, 1); Point second = new Point(2, 2); @@ -423,7 +423,7 @@ public void shouldCreateWithinQueryCorrectly() { } @Test // DATAMONGO-1110 - public void shouldCreateNearSphereQueryForSphericalProperty() { + void shouldCreateNearSphereQueryForSphericalProperty() { Point point = new Point(10, 20); @@ -435,7 +435,7 @@ public void shouldCreateNearSphereQueryForSphericalProperty() { } @Test // DATAMONGO-1110 - public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDefaultMetric() { + void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDefaultMetric() { Point point = new Point(1.0, 1.0); Distance distance = new Distance(1.0); @@ -448,7 +448,7 @@ public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDef } @Test // DATAMONGO-1110 - public void shouldCreateNearQueryForMinMaxDistance() { + void shouldCreateNearQueryForMinMaxDistance() { Point point = new Point(10, 20); Range range = Distance.between(new Distance(10), new Distance(20)); @@ -461,7 +461,7 @@ public void shouldCreateNearQueryForMinMaxDistance() { } @Test // DATAMONGO-1229 - public void appliesIgnoreCaseToLeafProperty() { + void appliesIgnoreCaseToLeafProperty() { PartTree tree = new PartTree("findByAddressStreetIgnoreCase", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "Street"); @@ -470,7 +470,7 @@ public void appliesIgnoreCaseToLeafProperty() { } @Test // DATAMONGO-1232 - public void ignoreCaseShouldEscapeSource() { + void ignoreCaseShouldEscapeSource() { PartTree tree = new PartTree("findByUsernameIgnoreCase", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "con.flux+"); @@ -481,7 +481,7 @@ public void ignoreCaseShouldEscapeSource() { } @Test // DATAMONGO-1232 - public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() { + void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() { PartTree tree = new PartTree("findByUsernameStartingWithIgnoreCase", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "dawns.light+"); @@ -492,7 +492,7 @@ public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() { } @Test // DATAMONGO-1232 - public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() { + void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() { PartTree tree = new PartTree("findByUsernameEndingWithIgnoreCase", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "new.ton+"); @@ -503,7 +503,7 @@ public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() { } @Test // DATAMONGO-1232 - public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { + void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "*fire.fight+*"); @@ -514,7 +514,7 @@ public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { } @Test // DATAMONGO-1232 - public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() { + void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "*steel.heart+"); @@ -525,7 +525,7 @@ public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() { } @Test // DATAMONGO-1232 - public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() { + void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "cala.mity+*"); @@ -535,7 +535,7 @@ public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() { } @Test // DATAMONGO-1232 - public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { + void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { PartTree tree = new PartTree("findByUsernameLike", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "*"); @@ -545,7 +545,7 @@ public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { } @Test // DATAMONGO-1342 - public void bindsNullValueToContainsClause() { + void bindsNullValueToContainsClause() { PartTree partTree = new PartTree("emailAddressesContains", User.class); @@ -556,7 +556,7 @@ public void bindsNullValueToContainsClause() { } @Test // DATAMONGO-1424 - public void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { + void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "*fire.fight+*"); @@ -568,7 +568,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { } @Test // DATAMONGO-1424 - public void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() { + void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "*steel.heart+"); @@ -580,7 +580,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() { } @Test // DATAMONGO-1424 - public void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() { + void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "cala.mity+*"), context); @@ -591,7 +591,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() { } @Test // DATAMONGO-1424 - public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { + void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, "*"); @@ -602,7 +602,7 @@ public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { } @Test // DATAMONGO-1588 - public void queryShouldAcceptSubclassOfDeclaredArgument() { + void queryShouldAcceptSubclassOfDeclaredArgument() { PartTree tree = new PartTree("findByLocationNear", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, new GeoJsonPoint(-74.044502D, 40.689247D)); @@ -612,7 +612,7 @@ public void queryShouldAcceptSubclassOfDeclaredArgument() { } @Test // DATAMONGO-1588 - public void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() { + void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() { PartTree tree = new PartTree("findByLocationNear", User.class); ConvertingParameterAccessor accessor = getAccessor(converter, @@ -623,7 +623,7 @@ public void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() { } @Test // DATAMONGO-2003 - public void createsRegexQueryForPatternCorrectly() { + void createsRegexQueryForPatternCorrectly() { PartTree tree = new PartTree("findByFirstNameRegex", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, Pattern.compile(".*")), context); @@ -632,7 +632,7 @@ public void createsRegexQueryForPatternCorrectly() { } @Test // DATAMONGO-2003 - public void createsRegexQueryForPatternWithOptionsCorrectly() { + void createsRegexQueryForPatternWithOptionsCorrectly() { Pattern pattern = Pattern.compile(".*", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); @@ -642,7 +642,7 @@ public void createsRegexQueryForPatternWithOptionsCorrectly() { } @Test // DATAMONGO-2071 - public void betweenShouldAllowSingleRageParameter() { + void betweenShouldAllowSingleRageParameter() { PartTree tree = new PartTree("findByAgeBetween", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, @@ -652,7 +652,7 @@ public void betweenShouldAllowSingleRageParameter() { } @Test // DATAMONGO-2394 - public void nearShouldUseMetricDistanceForGeoJsonTypes() { + void nearShouldUseMetricDistanceForGeoJsonTypes() { GeoJsonPoint point = new GeoJsonPoint(27.987901, 86.9165379); PartTree tree = new PartTree("findByLocationNear", User.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java index fe2f447d78..c6c1b140cd 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java @@ -38,6 +38,7 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.repository.MongoRepository; @@ -59,33 +60,32 @@ * @author Mark Paluch */ @ExtendWith(MockitoExtension.class) -public class PartTreeMongoQueryUnitTests { +class PartTreeMongoQueryUnitTests { @Mock MongoOperations mongoOperationsMock; @Mock ExecutableFind findOperationMock; - MongoMappingContext mappingContext; + private MongoMappingContext mappingContext; @BeforeEach - public void setUp() { + void setUp() { mappingContext = new MongoMappingContext(); - DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDatabaseFactory.class)); - MongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); + MongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); doReturn(converter).when(mongoOperationsMock).getConverter(); doReturn(findOperationMock).when(mongoOperationsMock).query(any()); } @Test // DATAMOGO-952 - public void rejectsInvalidFieldSpecification() { + void rejectsInvalidFieldSpecification() { assertThatIllegalStateException().isThrownBy(() -> deriveQueryFromMethod("findByLastname", "foo")) .withMessageContaining("findByLastname"); } @Test // DATAMOGO-952 - public void singleFieldJsonIncludeRestrictionShouldBeConsidered() { + void singleFieldJsonIncludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstname", "foo"); @@ -93,7 +93,7 @@ public void singleFieldJsonIncludeRestrictionShouldBeConsidered() { } @Test // DATAMOGO-952 - public void multiFieldJsonIncludeRestrictionShouldBeConsidered() { + void multiFieldJsonIncludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstnameAndLastname", "foo", "bar"); @@ -102,7 +102,7 @@ public void multiFieldJsonIncludeRestrictionShouldBeConsidered() { } @Test // DATAMOGO-952 - public void multiFieldJsonExcludeRestrictionShouldBeConsidered() { + void multiFieldJsonExcludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstnameAndLastname", "foo", "bar"); @@ -111,7 +111,7 @@ public void multiFieldJsonExcludeRestrictionShouldBeConsidered() { } @Test // DATAMOGO-973 - public void shouldAddFullTextParamCorrectlyToDerivedQuery() { + void shouldAddFullTextParamCorrectlyToDerivedQuery() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstname", "text", TextCriteria.forDefaultLanguage().matching("search")); @@ -120,19 +120,19 @@ public void shouldAddFullTextParamCorrectlyToDerivedQuery() { } @Test // DATAMONGO-1180 - public void propagatesRootExceptionForInvalidQuery() { + void propagatesRootExceptionForInvalidQuery() { assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> deriveQueryFromMethod("findByAge", 1)) .withCauseInstanceOf(JsonParseException.class); } @Test // DATAMONGO-1345, DATAMONGO-1735 - public void doesNotDeriveFieldSpecForNormalDomainType() { + void doesNotDeriveFieldSpecForNormalDomainType() { assertThat(deriveQueryFromMethod("findPersonBy", new Object[0]).getFieldsObject()).isEqualTo(new Document()); } @Test // DATAMONGO-1345 - public void restrictsQueryToFieldsRequiredForProjection() { + void restrictsQueryToFieldsRequiredForProjection() { Document fieldsObject = deriveQueryFromMethod("findPersonProjectedBy", new Object[0]).getFieldsObject(); @@ -141,7 +141,7 @@ public void restrictsQueryToFieldsRequiredForProjection() { } @Test // DATAMONGO-1345 - public void restrictsQueryToFieldsRequiredForDto() { + void restrictsQueryToFieldsRequiredForDto() { Document fieldsObject = deriveQueryFromMethod("findPersonDtoByAge", new Object[] { 42 }).getFieldsObject(); @@ -150,7 +150,7 @@ public void restrictsQueryToFieldsRequiredForDto() { } @Test // DATAMONGO-1345 - public void usesDynamicProjection() { + void usesDynamicProjection() { Document fields = deriveQueryFromMethod("findDynamicallyProjectedBy", ExtendedProjection.class).getFieldsObject(); @@ -160,7 +160,7 @@ public void usesDynamicProjection() { } @Test // DATAMONGO-1500 - public void shouldLeaveParameterConversionToQueryMapper() { + void shouldLeaveParameterConversionToQueryMapper() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findBySex", Sex.FEMALE); @@ -169,7 +169,7 @@ public void shouldLeaveParameterConversionToQueryMapper() { } @Test // DATAMONGO-1729, DATAMONGO-1735 - public void doesNotCreateFieldsObjectForOpenProjection() { + void doesNotCreateFieldsObjectForOpenProjection() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findAllBy"); @@ -177,12 +177,12 @@ public void doesNotCreateFieldsObjectForOpenProjection() { } @Test // DATAMONGO-1865 - public void limitingReturnsTrueIfTreeIsLimiting() { + void limitingReturnsTrueIfTreeIsLimiting() { assertThat(createQueryForMethod("findFirstBy").isLimiting()).isTrue(); } @Test // DATAMONGO-1865 - public void limitingReturnsFalseIfTreeIsNotLimiting() { + void limitingReturnsFalseIfTreeIsNotLimiting() { assertThat(createQueryForMethod("findPersonBy").isLimiting()).isFalse(); } diff --git a/spring-data-mongodb/src/test/resources/logback.xml b/spring-data-mongodb/src/test/resources/logback.xml index f154590864..a36841c97c 100644 --- a/spring-data-mongodb/src/test/resources/logback.xml +++ b/spring-data-mongodb/src/test/resources/logback.xml @@ -13,7 +13,6 @@ - diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 842dd8341b..eac49f37bc 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -4,7 +4,7 @@ [[new-features.3.3]] == What's New in Spring Data MongoDB 3.3 -* Extended support for <> entities. +* Extended support for <> entities. [[new-features.3.2]] == What's New in Spring Data MongoDB 3.2 diff --git a/src/main/asciidoc/reference/document-references.adoc b/src/main/asciidoc/reference/document-references.adoc new file mode 100644 index 0000000000..92badd2fa1 --- /dev/null +++ b/src/main/asciidoc/reference/document-references.adoc @@ -0,0 +1,440 @@ +[[mapping-usage-references]] +=== Using DBRefs + +The mapping framework does not have to store child objects embedded within the document. +You can also store them separately and use a `DBRef` to refer to that document. +When the object is loaded from MongoDB, those references are eagerly resolved so that you get back a mapped object that looks the same as if it had been stored embedded within your top-level document. + +The following example uses a DBRef to refer to a specific document that exists independently of the object in which it is referenced (both classes are shown in-line for brevity's sake): + +==== +[source,java] +---- +@Document +public class Account { + + @Id + private ObjectId id; + private Float total; +} + +@Document +public class Person { + + @Id + private ObjectId id; + @Indexed + private Integer ssn; + @DBRef + private List accounts; +} +---- +==== + +You need not use `@OneToMany` or similar mechanisms because the List of objects tells the mapping framework that you want a one-to-many relationship. +When the object is stored in MongoDB, there is a list of DBRefs rather than the `Account` objects themselves. +When it comes to loading collections of ``DBRef``s it is advisable to restrict references held in collection types to a specific MongoDB collection. +This allows bulk loading of all references, whereas references pointing to different MongoDB collections need to be resolved one by one. + +IMPORTANT: The mapping framework does not handle cascading saves. +If you change an `Account` object that is referenced by a `Person` object, you must save the `Account` object separately. +Calling `save` on the `Person` object does not automatically save the `Account` objects in the `accounts` property. + +``DBRef``s can also be resolved lazily. +In this case the actual `Object` or `Collection` of references is resolved on first access of the property. +Use the `lazy` attribute of `@DBRef` to specify this. +Required properties that are also defined as lazy loading ``DBRef`` and used as constructor arguments are also decorated with the lazy loading proxy making sure to put as little pressure on the database and network as possible. + +TIP: Lazily loaded ``DBRef``s can be hard to debug. +Make sure tooling does not accidentally trigger proxy resolution by e.g. calling `toString()` or some inline debug rendering invoking property getters. +Please consider to enable _trace_ logging for `org.springframework.data.mongodb.core.convert.DefaultDbRefResolver` to gain insight on `DBRef` resolution. + +[[mapping-usage.document-references]] +=== Using Document References + +Using `@DocumentReference` offers a flexible way of referencing entities in MongoDB. +While the goal is the same as when using <>, the store representation is different. +`DBRef` resolves to a document with a fixed structure as outlined in the https://docs.mongodb.com/manual/reference/database-references/[MongoDB Reference documentation]. + +Document references, do not follow a specific format. +They can be literally anything, a single value, an entire document, basically everything that can be stored in MongoDB. +By default, the mapping layer will use the referenced entities _id_ value for storage and retrieval, like in the sample below. + +==== +[source,java] +---- +@Document +class Account { + + @Id + String id; + Float total; +} + +@Document +class Person { + + @Id + String id; + + @DocumentReference <1> + List accounts; +} +---- + +[source,java] +---- +Account account = … + +tempate.insert(account); <2> + +template.update(Person.class) + .matching(where("id").is(…)) + .apply(new Update().push("accounts").value(account)) <3> + .first(); +---- + +[source,json] +---- +{ + "_id" : …, + "accounts" : [ "6509b9e" … ] <4> +} +---- +<1> Mark the collection of `Account` values to be referenced. +<2> The mapping framework does not handle cascading saves, so make sure to persist the referenced entity individually. +<3> Add the reference to the existing entity. +<4> Referenced `Account` entities are represented as an array of their `_id` values. +==== + +The sample above uses an ``_id``-based fetch query (`{ '_id' : ?#{#target} }`) for data retrieval and resolves linked entities eagerly. +It is possible to alter resolution defaults (listed below) using the attributes of `@DocumentReference` + +.@DocumentReference defaults +[cols="2,3,5",options="header"] +|=== +| Attribute | Description | Default + +| `db` +| The target database name for collection lookup. +| `MongoDatabaseFactory.getMongoDatabase()` + +| `collection` +| The target collection name. +| The annotated property's domain type, respectively the value type in case of `Collection` like or `Map` properties, collection name. + +| `lookup` +| The single document lookup query evaluating placeholders via SpEL expressions using `#target` as the marker for a given source value. `Collection` like or `Map` properties combine individual lookups via an `$or` operator. +| An `_id` field based query (`{ '_id' : ?#{#target} }`) using the loaded source value. + +| `sort` +| Used for sorting result documents on server side. +| None by default. +Result order of `Collection` like properties is restored based on the used lookup query on a best-effort basis. + +| `lazy` +| If set to `true` value resolution is delayed upon first access of the property. +| Resolves properties eagerly by default. +|=== + +`@DocumentReference(lookup)` allows defining filter queries that can be different from the `_id` field and therefore offer a flexible way of defining references between entities as demonstrated in the sample below, where the `Publisher` of a book is referenced by its acronym instead of the internal `id`. + +==== +[source,java] +---- +@Document +class Book { + + @Id + ObjectId id; + String title; + List author; + + @Field("publisher_ac") + @DocumentReference(lookup = "{ 'acronym' : ?#{#target} }") <1> + Publisher publisher; +} + +@Document +class Publisher { + + @Id + ObjectId id; + String acronym; <1> + String name; + + @DocumentReference(lazy = true) <2> + List books; + +} +---- + +.`Book` document +[source,json] +---- +{ + "_id" : 9a48e32, + "title" : "The Warded Man", + "author" : ["Peter V. Brett"], + "publisher_ac" : "DR" +} +---- + +.`Publisher` document +[source,json] +---- +{ + "_id" : 1a23e45, + "acronym" : "DR", + "name" : "Del Rey", + … +} +---- +<1> Use the `acronym` field to query for entities in the `Publisher` collection. +<2> Lazy load back references to the `Book` collection. +==== + +The above snippet shows the reading side of things when working with custom referenced objects. +Writing requires a bit of additional setup as the mapping information do not express where `#target` stems from. +The mapping layer requires registration of a `Converter` between the target document and `DocumentPointer`, like the one below: + +==== +[source,java] +---- +@WritingConverter +class PublisherReferenceConverter implements Converter> { + + @Override + public DocumentPointer convert(Publisher source) { + return () -> source.getAcronym(); + } +} +---- +==== + +If no `DocumentPointer` converter is provided the target reference document can be computed based on the given lookup query. +In this case the association target properties are evaluated as shown in the following sample. + +==== +[source,java] +---- +@Document +class Book { + + @Id + ObjectId id; + String title; + List author; + + @DocumentReference(lookup = "{ 'acronym' : ?#{acc} }") <1> <2> + Publisher publisher; +} + +@Document +class Publisher { + + @Id + ObjectId id; + String acronym; <1> + String name; + + // ... +} +---- + +[source,json] +---- +{ + "_id" : 9a48e32, + "title" : "The Warded Man", + "author" : ["Peter V. Brett"], + "publisher" : { + "acc" : "DOC" + } +} +---- +<1> Use the `acronym` field to query for entities in the `Publisher` collection. +<2> The field value placeholders of the lookup query (like `acc`) is used to form the reference document. +==== + +With all the above in place it is possible to model all kind of associations between entities. +Have a look at the non-exhaustive list of samples below to get feeling for what is possible. + +.Simple Document Reference using _id_ field +==== +[source,java] +---- +class Entity { + @DocumentReference + ReferencedObject ref; +} +---- + +[source,json] +---- +// entity +{ + "_id" : "8cfb002", + "ref" : "9a48e32" <1> +} + +// referenced object +{ + "_id" : "9a48e32" <1> +} +---- +<1> MongoDB simple type can be directly used without further configuration. +==== + +.Simple Document Reference using _id_ field with explicit lookup query +==== +[source,java] +---- +class Entity { + @DocumentReference(lookup = "{ '_id' : '?#{#target}' }") <1> + ReferencedObject ref; +} +---- + +[source,json] +---- +// entity +{ + "_id" : "8cfb002", + "ref" : "9a48e32" <1> +} + +// referenced object +{ + "_id" : "9a48e32" +} +---- +<1> _target_ defines the reference value itself. +==== + +.Document Reference extracting the `refKey` field for the lookup query +==== +[source,java] +---- +class Entity { + @DocumentReference(lookup = "{ '_id' : '?#{refKey}' }") <1> <2> + private ReferencedObject ref; +} +---- + +[source,java] +---- +@WritingConverter +class ToDocumentPointerConverter implements Converter> { + public DocumentPointer convert(ReferencedObject source) { + return () -> new Document("refKey", source.id); <1> + } +} +---- + +[source,json] +---- +// entity +{ + "_id" : "8cfb002", + "ref" : { + "refKey" : "9a48e32" <1> + } +} + +// referenced object +{ + "_id" : "9a48e32" +} +---- +<1> The key used for obtaining the reference value must be the one used during write. +<2> `refKey` is short for `target.refKey`. +==== + +.Document Reference with multiple values forming the lookup query +==== +[source,java] +---- +class Entity { + @DocumentReference(lookup = "{ 'firstname' : '?#{fn}', 'lastname' : '?#{ln}' }") <1> <2> + ReferencedObject ref; +} +---- + +[source,json] +---- +// entity +{ + "_id" : "8cfb002", + "ref" : { + "fn" : "Josh", <1> + "ln" : "Long" <1> + } +} + +// referenced object +{ + "_id" : "9a48e32", + "firsntame" : "Josh", <2> + "lastname" : "Long", <2> +} +---- +<1> Read/wirte the keys `fn` & `ln` from/to the linkage document based on the lookup query. +<2> Use non _id_ fields for the lookup of the target documents. +==== + +.Document Reference reading from a target collection +==== +[source,java] +---- +class Entity { + @DocumentReference(lookup = "{ '_id' : '?#{id}' }", collection = "?#{collection}") <2> + private ReferencedObject ref; +} +---- + +[source,java] +---- +@WritingConverter +class ToDocumentPointerConverter implements Converter> { + public DocumentPointer convert(ReferencedObject source) { + return () -> new Document("id", source.id) <1> + .append("collection", … ); <2> + } +} +---- + +[source,json] +---- +// entity +{ + "_id" : "8cfb002", + "ref" : { + "id" : "9a48e32", <1> + "collection" : "…" <2> + } +} +---- +<1> Read/wirte the keys `_id` from/to the reference document to use them in the lookup query. +<2> The collection name can be read from the reference document using its key. +==== + +[WARNING] +==== +We know it is tempting to use all kinds of MongoDB query operators in the lookup query and this is fine. +But there a few aspects to consider: + +* Make sure to have indexes in place that support your lookup. +* Mind that resolution requires a server rountrip inducing latency, consider a lazy strategy. +* A collection of document references is bulk loaded using the `$or` operator. + +The original element order is restored in memory on a best-effort basis. +Restoring the order is only possible when using equality expressions and cannot be done when using MongoDB query operators. +In this case results will be ordered as they are received from the store or via the provided `@DocumentReference(sort)` attribute. + +A few more general remarks: + +* Do you use cyclic references? +Ask your self if you need them. +* Lazy document references are hard to debug. +Make sure tooling does not accidentally trigger proxy resolution by e.g. calling `toString()`. +* There is no support for reading document references using reactive infrastructure. +==== diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index 9f9a461ee6..f08d03d3f0 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -2,7 +2,10 @@ [[mapping-chapter]] = Mapping -Rich mapping support is provided by the `MappingMongoConverter`. `MappingMongoConverter` has a rich metadata model that provides a full feature set to map domain objects to MongoDB documents. The mapping metadata model is populated by using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `MappingMongoConverter` also lets you map objects to documents without providing any additional metadata, by following a set of conventions. +Rich mapping support is provided by the `MappingMongoConverter`. `MappingMongoConverter` has a rich metadata model that provides a full feature set to map domain objects to MongoDB documents. +The mapping metadata model is populated by using annotations on your domain objects. +However, the infrastructure is not limited to using annotations as the only source of metadata information. +The `MappingMongoConverter` also lets you map objects to documents without providing any additional metadata, by following a set of conventions. This section describes the features of the `MappingMongoConverter`, including fundamentals, how to use conventions for mapping objects to documents and how to override those conventions with annotation-based mapping metadata. @@ -357,7 +360,10 @@ The `base-package` property tells it where to scan for classes annotated with th [[mapping-usage]] == Metadata-based Mapping -To take full advantage of the object mapping functionality inside the Spring Data MongoDB support, you should annotate your mapped objects with the `@Document` annotation. Although it is not necessary for the mapping framework to have this annotation (your POJOs are mapped correctly, even without any annotations), it lets the classpath scanner find and pre-process your domain objects to extract the necessary metadata. If you do not use this annotation, your application takes a slight performance hit the first time you store a domain object, because the mapping framework needs to build up its internal metadata model so that it knows about the properties of your domain object and how to persist them. The following example shows a domain object: +To take full advantage of the object mapping functionality inside the Spring Data MongoDB support, you should annotate your mapped objects with the `@Document` annotation. +Although it is not necessary for the mapping framework to have this annotation (your POJOs are mapped correctly, even without any annotations), it lets the classpath scanner find and pre-process your domain objects to extract the necessary metadata. +If you do not use this annotation, your application takes a slight performance hit the first time you store a domain object, because the mapping framework needs to build up its internal metadata model so that it knows about the properties of your domain object and how to persist them. +The following example shows a domain object: .Example domain object ==== @@ -759,7 +765,12 @@ mongoOperations.indexOpsFor(Jedi.class) NOTE: The text index feature is disabled by default for MongoDB v.2.4. -Creating a text index allows accumulating several fields into a searchable full-text index. It is only possible to have one text index per collection, so all fields marked with `@TextIndexed` are combined into this index. Properties can be weighted to influence the document score for ranking results. The default language for the text index is English. To change the default language, set the `language` attribute to whichever language you want (for example,`@Document(language="spanish")`). Using a property called `language` or `@Language` lets you define a language override on a per document base. The following example shows how to created a text index and set the language to Spanish: +Creating a text index allows accumulating several fields into a searchable full-text index. +It is only possible to have one text index per collection, so all fields marked with `@TextIndexed` are combined into this index. +Properties can be weighted to influence the document score for ranking results. +The default language for the text index is English.To change the default language, set the `language` attribute to whichever language you want (for example,`@Document(language="spanish")`). +Using a property called `language` or `@Language` lets you define a language override on a per-document base. +The following example shows how to created a text index and set the language to Spanish: .Example Text Index Usage ==== @@ -783,417 +794,7 @@ class Nested { ---- ==== -[[mapping-usage-references]] -=== Using DBRefs - -The mapping framework does not have to store child objects embedded within the document. -You can also store them separately and use a DBRef to refer to that document. -When the object is loaded from MongoDB, those references are eagerly resolved so that you get back a mapped object that looks the same as if it had been stored embedded within your top-level document. - -The following example uses a DBRef to refer to a specific document that exists independently of the object in which it is referenced (both classes are shown in-line for brevity's sake): - -==== -[source,java] ----- -@Document -public class Account { - - @Id - private ObjectId id; - private Float total; -} - -@Document -public class Person { - - @Id - private ObjectId id; - @Indexed - private Integer ssn; - @DBRef - private List accounts; -} ----- -==== - -You need not use `@OneToMany` or similar mechanisms because the List of objects tells the mapping framework that you want a one-to-many relationship. When the object is stored in MongoDB, there is a list of DBRefs rather than the `Account` objects themselves. -When it comes to loading collections of ``DBRef``s it is advisable to restrict references held in collection types to a specific MongoDB collection. This allows bulk loading of all references, whereas references pointing to different MongoDB collections need to be resolved one by one. - -IMPORTANT: The mapping framework does not handle cascading saves. If you change an `Account` object that is referenced by a `Person` object, you must save the `Account` object separately. Calling `save` on the `Person` object does not automatically save the `Account` objects in the `accounts` property. - -``DBRef``s can also be resolved lazily. In this case the actual `Object` or `Collection` of references is resolved on first access of the property. Use the `lazy` attribute of `@DBRef` to specify this. -Required properties that are also defined as lazy loading ``DBRef`` and used as constructor arguments are also decorated with the lazy loading proxy making sure to put as little pressure on the database and network as possible. - -TIP: Lazily loaded ``DBRef``s can be hard to debug. Make sure tooling does not accidentally trigger proxy resolution by eg. calling `toString()` or some inline debug rendering invoking property getters. -Please consider to enable _trace_ logging for `org.springframework.data.mongodb.core.convert.DefaultDbRefResolver` to gain insight on `DBRef` resolution. - -[[mapping-usage.linking]] -=== Using Document References - -Using `@DocumentReference` offers an alternative way of linking entities in MongoDB. -While the goal is the same as when using <>, the store representation is different. -`DBRef` resolves to a document with a fixed structure as outlined in the https://docs.mongodb.com/manual/reference/database-references/[MongoDB Reference documentation]. + -Document references, do not follow a specific format. -They can be literally anything, a single value, an entire document, basically everything that can be stored in MongoDB. -By default, the mapping layer will use the referenced entities _id_ value for storage and retrieval, like in the sample below. - -==== -[source,java] ----- -@Document -public class Account { - - @Id - private String id; - private Float total; -} - -@Document -public class Person { - - @Id - private String id; - - @DocumentReference <1> - private List accounts; -} ----- -[source,java] ----- -Account account = ... - -tempate.insert(account); <2> - -template.update(Person.class) - .matching(where("id").is(...)) - .apply(new Update().push("accounts").value(account)) <3> - .first(); ----- -[source,json] ----- -{ - "_id" : ..., - "accounts" : [ "6509b9e", ... ] <4> -} ----- -<1> Mark the collection of `Account` values to be linked. -<2> The mapping framework does not handle cascading saves, so make sure to persist the referenced entity individually. -<3> Add the reference to the existing entity. -<4> Linked `Account` entities are represented as an array of their `_id` values. -==== - -The sample above uses an `_id` based fetch query (`{ '_id' : ?#{#target} }`) for data retrieval and resolves linked entities eagerly. -It is possible to alter resolution defaults (listed below) via the attributes of `@DocumentReference` - -.@DocumentReference defaults -[cols="2,3,5", options="header"] -|=== -| Attribute | Description | Default - -| `db` -| The target database name for collection lookup. -| The configured database provided by `MongoDatabaseFactory.getMongoDatabase()`. - -| `collection` -| The target collection name. -| The annotated properties domain type, respectively the value type in case of `Collection` like or `Map` properties, collection name. - -| `lookup` -| The single document lookup query evaluating placeholders via SpEL expressions using `#target` as the marker for a given source value. `Collection` like or `Map` properties combine individual lookups via an `$or` operator. -| An `_id` field based query (`{ '_id' : ?#{#target} }`) using the loaded source value. - -| `sort` -| Used for sorting result documents on server side. -| None by default. Result order of `Collection` like properties is restored based on the used lookup query. - -| `lazy` -| If set to `true` value resolution is delayed upon first access of the property. -| Resolves properties eagerly by default. -|=== - -`@DocumentReference(lookup=...)` allows to define custom queries that are independent from the `_id` field and therefore offer a flexible way of defining links between entities as demonstrated in the sample below, where the `Publisher` of a book is referenced by its acronym instead of the internal `id`. - -==== -[source,java] ----- -@Document -public class Book { - - @Id - private ObjectId id; - private String title; - private List author; - - @Field("publisher_ac") - @DocumentReference(lookup = "{ 'acronym' : ?#{#target} }") <1> - private Publisher publisher; -} - -@Document -public class Publisher { - - @Id - private ObjectId id; - private String acronym; <1> - private String name; - - @DocumentReference(lazy = true) <2> - private List books; - -} ----- -[source,json] ----- -{ - "_id" : 9a48e32, - "title" : "The Warded Man", - "author" : ["Peter V. Brett"], - "publisher_ac" : "DR" -} ----- -<1> Use the `acronym` field to query for entities in the `Publisher` collection. -<2> Lazy load back references to the `Book` collection. -==== - -The above snipped shows the reading side of things when working with custom linked objects. -To make the writing part aware of the modified document pointer a custom converter, capable of the transformation into a `DocumentPointer`, like the one below, needs to be registered. - -==== -[source,java] ----- -@WritingConverter -class PublisherReferenceConverter implements Converter> { - - @Override - public DocumentPointer convert(Publisher source) { - return () -> source.getAcronym(); - } -} ----- -==== - -If no `DocumentPointer` converter is provided the target linkage document can be computed based on the given lookup query. -In this case the association target properties are evaluated as shown in the following sample. - -==== -[source,java] ----- -@Document -public class Book { - - @Id - private ObjectId id; - private String title; - private List author; - - @DocumentReference(lookup = "{ 'acronym' : ?#{acc} }") <1> <2> - private Publisher publisher; -} - -@Document -public class Publisher { - - @Id - private ObjectId id; - private String acronym; <1> - private String name; - - // ... -} ----- -[source,json] ----- -{ - "_id" : 9a48e32, - "title" : "The Warded Man", - "author" : ["Peter V. Brett"], - "publisher" : { - "acc" : "DOC" - } -} ----- -<1> Use the `acronym` field to query for entities in the `Publisher` collection. -<2> The field value placeholders of the lookup query (like `acc`) is used to form the linkage document. -==== - -With all the above in place it is possible to model all kind of associations between entities. -Have a look at the non exhaustive list of samples below to get feeling for what is possible. - -.Simple Document Reference using _id_ field -==== -[source,java] ----- -class Entity { - @DocumentReference - private ReferencedObject ref; -} ----- - -[source,json] ----- -// entity -{ - "_id" : "8cfb002", - "ref" : "9a48e32" <1> -} - -// referenced object -{ - "_id" : "9a48e32" <1> -} ----- -<1> MongoDB simple type can be directly used without further configuration. -==== - -.Simple Document Reference using _id_ field with explicit lookup query -==== -[source,java] ----- -class Entity { - @DocumentReference(lookup = "{ '_id' : '?#{#target}' }") <1> - private ReferencedObject ref; -} ----- - -[source,json] ----- -// entity -{ - "_id" : "8cfb002", - "ref" : "9a48e32" <1> -} - -// referenced object -{ - "_id" : "9a48e32" -} ----- -<1> _target_ defines the linkage value itself. -==== - -.Document Reference extracting field of linkage document for lookup query -==== -[source,java] ----- -class Entity { - @DocumentReference(lookup = "{ '_id' : '?#{refKey}' }") <1> <2> - private ReferencedObject ref; -} ----- - -[source,java] ----- -@WritingConverter -class ToDocumentPointerConverter implements Converter> { - public DocumentPointer convert(ReferencedObject source) { - return () -> new Document("refKey", source.id); <1> - } -} ----- - -[source,json] ----- -// entity -{ - "_id" : "8cfb002", - "ref" : { - "refKey" : "9a48e32" <1> - } -} - -// referenced object -{ - "_id" : "9a48e32" -} ----- -<1> The key used for obtaining the linkage value must be the one used during write. -<2> `refKey` is short for `target.refKey`. -==== - -.Document Reference with multiple values forming the lookup query -==== -[source,java] ----- -class Entity { - @DocumentReference(lookup = "{ 'firstname' : '?#{fn}', 'lastname' : '?#{ln}' }") <1> <2> - private ReferencedObject ref; -} ----- - -[source,json] ----- -// entity -{ - "_id" : "8cfb002", - "ref" : { - "fn" : "Josh", <1> - "ln" : "Long" <1> - } -} - -// referenced object -{ - "_id" : "9a48e32", - "firsntame" : "Josh", <2> - "lastname" : "Long", <2> -} ----- -<1> Read/wirte the keys `fn` & `ln` from/to the linkage document based on the lookup query. -<2> Use non _id_ fields for the lookup of the target documents. -==== - -.Document Reference reading target collection from linkage document -==== -[source,java] ----- -class Entity { - @DocumentReference(lookup = "{ '_id' : '?#{id}' }", collection = "?#{collection}") <2> - private ReferencedObject ref; -} ----- - -[source,java] ----- -@WritingConverter -class ToDocumentPointerConverter implements Converter> { - public DocumentPointer convert(ReferencedObject source) { - return () -> new Document("id", source.id) <1> - .append("collection", ... ); <2> - } -} ----- - -[source,json] ----- -// entity -{ - "_id" : "8cfb002", - "ref" : { - "id" : "9a48e32", <1> - "collection" : "..." <2> - } -} ----- -<1> Read/wirte the keys `_id` from/to the linkage document to use them in the lookup query. -<2> The collection name can be read from the linkage document via its key. -==== - -[WARNING] -==== -We know it is tempting to use all kinds of MongoDB query operators in the lookup query and this is fine. But: - -* Make sure to have indexes in place that support your lookup. -* Mind that resolution takes time and consider a lazy strategy. -* A collection of document references is bulk loaded using an `$or` operator. + -The original element order is restored in memory which cannot be done when using MongoDB query operators. -In this case Results will be ordered as they are received from the store or via the provided `@DocumentReference(sort = ...)` attribute. - -And a few more general remarks: - -* Cyclic references? Ask your self if you need them. -* Lazy document references are hard to debug. Make sure tooling does not accidentally trigger proxy resolution by eg. calling `toString()`. -* There is no support for reading document references via the reactive bits Spring Data MongoDB offers. -==== +include::document-references.adoc[] [[mapping-usage-events]] === Mapping Framework Events