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.
This commit is contained in:
Mark Paluch
2021-05-21 10:55:59 +02:00
parent e96ef8e18f
commit 5ab75eb65a
23 changed files with 680 additions and 654 deletions

View File

@@ -546,8 +546,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
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));
}

View File

@@ -29,8 +29,9 @@ import com.mongodb.client.MongoCollection;
/**
* {@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 {

View File

@@ -28,7 +28,7 @@ import com.mongodb.client.MongoCollection;
/**
* 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 @@ public interface ReferenceLoader {
return new Document();
}
// TODO: Move apply method into something else that holds the collection and knows about single item/multi-item
default Iterable<Document> apply(MongoCollection<Document> collection) {
return restoreOrder(collection.find(getQuery()).sort(getSort()));
}

View File

@@ -57,6 +57,7 @@ import com.mongodb.client.MongoCollection;
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 3.3
*/
public final class ReferenceLookupDelegate {

View File

@@ -17,8 +17,9 @@ package org.springframework.data.mongodb.core.mapping;
/**
* 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<T> {
@@ -27,7 +28,7 @@ public interface DocumentPointer<T> {
* 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();

View File

@@ -22,13 +22,14 @@ import java.lang.annotation.RetentionPolicy;
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.
*
* <pre class="code">
* public class Account {
* private String id;
@@ -40,7 +41,7 @@ import org.springframework.data.annotation.Reference;
* &#64;DocumentReference
* private List&lt;Account&gt; accounts;
* }
*
*
* Account account = ...
*
* mongoTemplate.insert(account);
@@ -50,43 +51,41 @@ import org.springframework.data.annotation.Reference;
* .apply(new Update().push("accounts").value(account))
* .first();
* </pre>
*
* {@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.
*
* <pre class="code">
* public class Book {
* private ObjectId id;
* private String title;
* private ObjectId id;
* private String title;
*
* &#64;Field("publisher_ac")
* &#64;DocumentReference(lookup = "{ 'acronym' : ?#{#target} }")
* private Publisher publisher;
* &#64;Field("publisher_ac") &#64;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;
*
* &#64;DocumentReference(lazy = true)
* private List&lt;Book&gt; books;
* &#64;DocumentReference(lazy = true) private List&lt;Book&gt; books;
* }
*
* &#64;WritingConverter
* public class PublisherReferenceConverter implements Converter&lt;Publisher, DocumentPointer&lt;String&gt;&gt; {
*
* public DocumentPointer&lt;String&gt; convert(Publisher source) {
* public DocumentPointer&lt;String&gt; convert(Publisher source) {
* return () -> source.getAcronym();
* }
* }
* }
* </pre>
*
* @author Christoph Strobl
* @since 3.3
* @see <a href="https://docs.mongodb.com/manual/reference/database-references/#std-label-document-references">MongoDB Reference Documentation</a>
* @see <a href="https://docs.mongodb.com/manual/reference/database-references/#std-label-document-references">MongoDB
* Reference Documentation</a>
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@@ -95,22 +94,25 @@ import org.springframework.data.annotation.Reference;
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 @@ public @interface DocumentReference {
/**
* A specific sort.
*
* @return empty String by default.
*/
String sort() default "";

View File

@@ -25,6 +25,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
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 org.springframework.transaction.support.TransactionTemplate;
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 class MongoTransactionManagerUnitTests {
}
@Test // DATAMONGO-1920
public void participateInOnGoingTransactionWithCommit() {
void participateInOnGoingTransactionWithCommit() {
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
@@ -126,7 +132,7 @@ public class MongoTransactionManagerUnitTests {
}
@Test // DATAMONGO-1920
public void participateInOnGoingTransactionWithRollbackOnly() {
void participateInOnGoingTransactionWithRollbackOnly() {
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
@@ -163,7 +169,7 @@ public class MongoTransactionManagerUnitTests {
}
@Test // DATAMONGO-1920
public void triggerRollbackCorrectly() {
void triggerRollbackCorrectly() {
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
@@ -185,7 +191,7 @@ public class MongoTransactionManagerUnitTests {
}
@Test // DATAMONGO-1920
public void suspendTransactionWhilePropagationNotSupported() {
void suspendTransactionWhilePropagationNotSupported() {
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
@@ -228,7 +234,7 @@ public class MongoTransactionManagerUnitTests {
}
@Test // DATAMONGO-1920
public void suspendTransactionWhilePropagationRequiresNew() {
void suspendTransactionWhilePropagationRequiresNew() {
when(dbFactory.withSession(session2)).thenReturn(dbFactory2);
when(dbFactory2.getMongoDatabase()).thenReturn(db2);
@@ -277,7 +283,7 @@ public class MongoTransactionManagerUnitTests {
}
@Test // DATAMONGO-1920
public void readonlyShouldInitiateASessionStartAndCommitTransaction() {
void readonlyShouldInitiateASessionStartAndCommitTransaction() {
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
@@ -303,7 +309,7 @@ public class MongoTransactionManagerUnitTests {
}
@Test // DATAMONGO-1920
public void readonlyShouldInitiateASessionStartAndRollbackTransaction() {
void readonlyShouldInitiateASessionStartAndRollbackTransaction() {
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);

View File

@@ -27,6 +27,7 @@ import org.springframework.data.geo.Point;
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 @@ import org.springframework.data.mongodb.core.query.Query;
* @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 class CountQueryUnitTests {
}
@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 class CountQueryUnitTests {
}
@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 class CountQueryUnitTests {
}
@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 class CountQueryUnitTests {
}
@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 class CountQueryUnitTests {
}
@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 class CountQueryUnitTests {
}
@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 class CountQueryUnitTests {
}
@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 class CountQueryUnitTests {
}
@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)));

View File

@@ -1094,17 +1094,17 @@ public class MongoTemplateDocumentReferenceTests {
}
}
static class ReferencableConverter implements Converter<ReferenceAble, DocumentPointer> {
static class ReferencableConverter implements Converter<ReferenceAble, DocumentPointer<Object>> {
@Nullable
@Override
public DocumentPointer convert(ReferenceAble source) {
public DocumentPointer<Object> convert(ReferenceAble source) {
return source::toReference;
}
}
@WritingConverter
class DocumentToSimpleObjectRefWithReadingConverter
static class DocumentToSimpleObjectRefWithReadingConverter
implements Converter<DocumentPointer<Document>, SimpleObjectRefWithReadingConverter> {
@Nullable
@@ -1118,7 +1118,7 @@ public class MongoTemplateDocumentReferenceTests {
}
@WritingConverter
class SimpleObjectRefWithReadingConverterToDocumentConverter
static class SimpleObjectRefWithReadingConverterToDocumentConverter
implements Converter<SimpleObjectRefWithReadingConverter, DocumentPointer<Document>> {
@Nullable

View File

@@ -32,6 +32,7 @@ import org.springframework.data.mongodb.MongoDatabaseFactory;
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 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@ExtendWith(MockitoExtension.class)
class FilterExpressionUnitTests {
@Mock MongoDatabaseFactory mongoDbFactory;
private AggregationOperationContext aggregationContext;
private MongoMappingContext mappingContext;
@@ -51,7 +50,7 @@ class FilterExpressionUnitTests {
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

View File

@@ -49,7 +49,6 @@ class CustomConvertersUnitTests {
@Mock BarToDocumentConverter barToDocumentConverter;
@Mock DocumentToBarConverter documentToBarConverter;
@Mock MongoDatabaseFactory mongoDbFactory;
private MongoMappingContext context;
@@ -67,7 +66,7 @@ class CustomConvertersUnitTests {
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
context.initialize();
converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), context);
converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
converter.setCustomConversions(conversions);
converter.afterPropertiesSet();
}

View File

@@ -59,18 +59,18 @@ import com.mongodb.client.MongoDatabase;
@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 class MappingMongoConverterTests {
}
@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 class MappingMongoConverterTests {
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 class MappingMongoConverterTests {
}
@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 class MappingMongoConverterTests {
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 class MappingMongoConverterTests {
}
@Test // DATAMONGO-2400
public void readJavaTimeValuesWrittenViaCodec() {
void readJavaTimeValuesWrittenViaCodec() {
configureConverterWithNativeJavaTimeCodec();
MongoCollection<Document> mongoCollection = client.getDatabase(DATABASE).getCollection("java-time-types");
@@ -160,7 +158,7 @@ public class MappingMongoConverterTests {
@Id String id;
@DBRef(lazy = true) List<Sample> lazyList;
public List<Sample> getLazyList() {
List<Sample> getLazyList() {
return lazyList;
}
}
@@ -176,7 +174,7 @@ public class MappingMongoConverterTests {
this.lazyList = lazyList;
}
public List<Sample> getLazyList() {
List<Sample> getLazyList() {
return lazyList;
}
}

View File

@@ -54,27 +54,25 @@ import org.springframework.data.util.TypeInformation;
* @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 class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() {
FlatDocument probe = new FlatDocument();
probe.id = "steelheart";
@@ -98,7 +96,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -110,7 +108,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() {
FlatDocument probe = new FlatDocument();
probe.listOfString = Arrays.asList("Prof", "Tia", "David");
@@ -122,7 +120,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() {
FlatDocument probe = new FlatDocument();
probe.customNamedField = "Mitosis";
@@ -132,7 +130,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void typedExampleShouldContainTypeRestriction() {
void typedExampleShouldContainTypeRestriction() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
@@ -146,7 +144,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() {
void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
@@ -157,7 +155,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() {
void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
@@ -170,7 +168,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -184,7 +182,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() {
void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "fire.ight";
@@ -198,7 +196,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -212,7 +210,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -226,7 +224,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -240,7 +238,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() {
void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -255,7 +253,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedWhenContainingDBRef() {
void exampleShouldBeMappedWhenContainingDBRef() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "steelheart";
@@ -271,7 +269,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedWhenDBRefIsNull() {
void exampleShouldBeMappedWhenDBRefIsNull() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "steelheart";
@@ -283,7 +281,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() {
void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() {
ClassWithGeoTypes probe = new ClassWithGeoTypes();
probe.legacyPoint = new Point(10D, 20D);
@@ -296,7 +294,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void mappingShouldExcludeFieldWithCustomNameCorrectly() {
void mappingShouldExcludeFieldWithCustomNameCorrectly() {
FlatDocument probe = new FlatDocument();
probe.customNamedField = "foo";
@@ -311,7 +309,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void mappingShouldExcludeFieldCorrectly() {
void mappingShouldExcludeFieldCorrectly() {
FlatDocument probe = new FlatDocument();
probe.customNamedField = "foo";
@@ -326,7 +324,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void mappingShouldExcludeNestedFieldCorrectly() {
void mappingShouldExcludeNestedFieldCorrectly() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
@@ -342,7 +340,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() {
void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
@@ -358,7 +356,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() {
void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -372,7 +370,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1245
public void mappingShouldIncludePropertiesFromHierarchicalDocument() {
void mappingShouldIncludePropertiesFromHierarchicalDocument() {
HierachicalDocument probe = new HierachicalDocument();
probe.stringValue = "firefight";
@@ -386,7 +384,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1459
public void mapsAnyMatchingExampleCorrectly() {
void mapsAnyMatchingExampleCorrectly() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
@@ -398,7 +396,7 @@ public class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1768
public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPath() {
void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPath() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
@@ -411,13 +409,13 @@ public class MongoExampleMapperUnitTests {
}
@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 class MongoExampleMapperUnitTests {
}
@Test // DATAMONGO-1768
public void untypedExampleShouldNotInferTypeRestriction() {
void untypedExampleShouldNotInferTypeRestriction() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();

View File

@@ -34,14 +34,13 @@ import org.bson.types.ObjectId;
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 @@ public class QueryMapperUnitTests {
}
// 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());
}
}

View File

@@ -48,6 +48,7 @@ import org.springframework.data.domain.Sort.Order;
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 @@ import com.mongodb.DBRef;
@ExtendWith(MockitoExtension.class)
class UpdateMapperUnitTests {
@Mock MongoDatabaseFactory factory;
private MappingMongoConverter converter;
private MongoMappingContext context;
private UpdateMapper mapper;
@@ -88,7 +88,7 @@ class UpdateMapperUnitTests {
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();

View File

@@ -45,6 +45,7 @@ import org.springframework.data.domain.Sort.Direction;
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 @@ class AbstractMongoQueryUnitTests {
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();

View File

@@ -28,6 +28,7 @@ import org.mockito.Mock;
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 @@ import com.mongodb.BasicDBList;
* @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 class ConvertingParameterAccessorUnitTests {
}
@Test // DATAMONGO-505
public void convertsAssociationsToDBRef() {
void convertsAssociationsToDBRef() {
Property property = new Property();
property.id = 5L;
@@ -102,7 +104,7 @@ public class ConvertingParameterAccessorUnitTests {
}
@Test // DATAMONGO-505
public void convertsAssociationsToDBRefForCollections() {
void convertsAssociationsToDBRefForCollections() {
Property property = new Property();
property.id = 5L;

View File

@@ -38,12 +38,14 @@ import org.springframework.data.geo.Polygon;
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 @@ import org.springframework.data.repository.query.parser.PartTree;
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class MongoQueryCreatorUnitTests {
class MongoQueryCreatorUnitTests {
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
MongoConverter converter;
private MappingContext<? extends MongoPersistentEntity<?>, 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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-347
public void createsQueryReferencingADBRefCorrectly() {
void createsQueryReferencingADBRefCorrectly() {
User user = new User();
user.id = new ObjectId();
@@ -222,7 +222,7 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-1110
public void shouldCreateNearSphereQueryForSphericalProperty() {
void shouldCreateNearSphereQueryForSphericalProperty() {
Point point = new Point(10, 20);
@@ -435,7 +435,7 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-1110
public void shouldCreateNearQueryForMinMaxDistance() {
void shouldCreateNearQueryForMinMaxDistance() {
Point point = new Point(10, 20);
Range<Distance> range = Distance.between(new Distance(10), new Distance(20));
@@ -461,7 +461,7 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-1232
public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {
void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*");
@@ -545,7 +545,7 @@ public class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-1342
public void bindsNullValueToContainsClause() {
void bindsNullValueToContainsClause() {
PartTree partTree = new PartTree("emailAddressesContains", User.class);
@@ -556,7 +556,7 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-1424
public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {
void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {
PartTree tree = new PartTree("findByUsernameNotLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*");
@@ -602,7 +602,7 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-1588
public void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() {
void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() {
PartTree tree = new PartTree("findByLocationNear", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter,
@@ -623,7 +623,7 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-2003
public void createsRegexQueryForPatternWithOptionsCorrectly() {
void createsRegexQueryForPatternWithOptionsCorrectly() {
Pattern pattern = Pattern.compile(".*", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
@@ -642,7 +642,7 @@ public class MongoQueryCreatorUnitTests {
}
@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 class MongoQueryCreatorUnitTests {
}
@Test // DATAMONGO-2394
public void nearShouldUseMetricDistanceForGeoJsonTypes() {
void nearShouldUseMetricDistanceForGeoJsonTypes() {
GeoJsonPoint point = new GeoJsonPoint(27.987901, 86.9165379);
PartTree tree = new PartTree("findByLocationNear", User.class);

View File

@@ -38,6 +38,7 @@ 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.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.mongodb.repository.MongoRepository;
@@ -59,33 +60,32 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
* @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 class PartTreeMongoQueryUnitTests {
}
@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 class PartTreeMongoQueryUnitTests {
}
@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 class PartTreeMongoQueryUnitTests {
}
@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 class PartTreeMongoQueryUnitTests {
}
@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 class PartTreeMongoQueryUnitTests {
}
@Test // DATAMONGO-1345
public void restrictsQueryToFieldsRequiredForDto() {
void restrictsQueryToFieldsRequiredForDto() {
Document fieldsObject = deriveQueryFromMethod("findPersonDtoByAge", new Object[] { 42 }).getFieldsObject();
@@ -150,7 +150,7 @@ public class PartTreeMongoQueryUnitTests {
}
@Test // DATAMONGO-1345
public void usesDynamicProjection() {
void usesDynamicProjection() {
Document fields = deriveQueryFromMethod("findDynamicallyProjectedBy", ExtendedProjection.class).getFieldsObject();
@@ -160,7 +160,7 @@ public class PartTreeMongoQueryUnitTests {
}
@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 class PartTreeMongoQueryUnitTests {
}
@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 class PartTreeMongoQueryUnitTests {
}
@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();
}

View File

@@ -13,7 +13,6 @@
<logger name="org.springframework.data.mongodb.core" level="error"/>
<logger name="org.springframework.data.mongodb.test.util" level="info"/>
<!-- <logger name="org.springframework.data.mongodb.core.convert" level="trace"/>-->
<root level="error">
<appender-ref ref="console" />

View File

@@ -4,7 +4,7 @@
[[new-features.3.3]]
== What's New in Spring Data MongoDB 3.3
* Extended support for <<mapping-usage.linking, linking>> entities.
* Extended support for <<mapping-usage.document-references, referencing>> entities.
[[new-features.3.2]]
== What's New in Spring Data MongoDB 3.2

View File

@@ -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<Account> 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 <<mapping-usage-references,DBRefs>>, 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<Account> 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<String> 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<Book> 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<Publisher, DocumentPointer<String>> {
@Override
public DocumentPointer<String> 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<String> 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<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> 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<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> 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.
====

View File

@@ -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<Account> 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 <<mapping-usage-references,DBRefs>>, 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<Account> 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<String> 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<Book> 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<Publisher, DocumentPointer<String>> {
@Override
public DocumentPointer<String> 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<String> 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<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> 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<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> 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