Spring certified China Education Management Center - Spring Data MongoDB tutorial 14

Original title: Spring certified China Education Management Center - Spring Data MongoDB tutorial 14 (content source: Spring China Education Management Center)

18.5.6. Wildcard index

AWildcardIndex is an index that can be used to contain all fields or specific fields based on a given (wildcard) pattern. For more information, see the MongoDB documentation.

Index IndexOperations can be set programmatically using wildcard indexvia.

Example 189. Programming wildcard index settings

mongoOperations
    .indexOps(User.class)
    .ensureIndex(new WildcardIndex("userMetadata"));
db.user.createIndex({ "userMetadata.$**" : 1 }, {})

The @ WildcardIndex annotation allows you to set it with the document type or attribute or with a declarative index.

If it is placed on the root domain entity type (annotated type @ Document), the index parser will create a wildcard index for it.

Example 190. Wildcard index of domain type

@Document
@WildcardIndexed
public class Product {
	// ...
}
db.product.createIndex({ "$**" : 1 },{})

The wildcardProjection can be used to specify key input / exclusion in the index.

Example 191. Wildcard index wildcardProjection

@Document
@WildcardIndexed(wildcardProjection = "{ 'userMetadata.age' : 0 }")
public class User {
    private @Id String id;
    private UserMetadata userMetadata;
}
db.user.createIndex(
  { "$**" : 1 },
  { "wildcardProjection" :
    { "userMetadata.age" : 0 }
  }
)

Wildcard indexes can also be represented by adding comments directly to fields. Note that wildcardProjection is not allowed on nested paths (such as properties). @ WildcardIndexed omits projection of annotated types during index creation.

Example 192. Wildcard index on attribute

@Document
public class User {
    private @Id String id;

    @WildcardIndexed
    private UserMetadata userMetadata;
}
db.user.createIndex({ "userMetadata.$**" : 1 }, {})

18.5.7. Text index

MongoDB v.2.4 disables text indexing by default.

Creating a text index allows multiple fields to be accumulated into a searchable full-text index. There can be only one text index per collection, so all fields marked @ TextIndexed are merged into this index. Properties can be weighted to affect the document score of ranking results. The default Language for text indexes is English. To change the default Language, set the Language property to Any Language you want (for example, @ Document(language="spanish"). Using the attribute @ Language named languageor, you can define Language overrides on a per document basis. The following example shows how to create a text index and set the Language to Spanish:

Example 193. Example text index usage

@Document(language = "spanish")
class SomeEntity {

    @TextIndexed String foo;

    @Language String lang;

    Nested nested;
}

class Nested {

    @TextIndexed(weight=5) String bar;
    String roo;
}

18.5.8. Using DBRefs

The mapping framework does not have to store child objects embedded in the document. You can also store them separately and use aDBRef to reference the document. When objects are loaded from MongoDB, these references are eagerly resolved so that you return a mapping object that looks the same as the storage embedded in the top-level document.

The following example uses DBRef to refer to a specific document that exists independently of the object that references it (for brevity, both classes are shown as inline):

@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 do not need to use @ OneToMany or a similar mechanism because the object list tells the mapping framework that you want a one to many relationship. When objects are stored in MongoDB, there is a list of DBRefs instead of the Account object itself. When loading a collection of DBRefs, it is recommended to limit the references saved in the collection type to a specific MongoDB collection. This allows bulk loading of all references, rather than the References to different MongoDB sets need to be resolved one by one.

The mapping framework does not handle cascading saves. If you change the Person object referenced by the Account object, you must save the object separately. Calling the Person object on save does not automatically save the accounts attribute of the Account object.

DBRefs can also be solved lazily. In this case, the actual Object or Collection reference referenced is resolved when the property is accessed for the first time. This is specified by using the lazy property @ DBRef. It is also defined as a required property that delays the loading of DBRef and is used as a constructor parameter. It is also decorated with a delay loading agent to ensure that the pressure on the database and network is minimized.

Deferred loaded DBRefs can be difficult to debug. Make sure the tool does not accidentally trigger proxy resolution by calling toString() or some inline debug render call property getter s, for example. Consider enabling trace logging org.springframework.data.mongodb.core.convert.DefaultDbRefResolver to learn more about DBRef solutions.

Delayed loading may require a class proxy. Conversely, since JEP 396: strongly encapsulated jdk internals by default, starting from Java 16 +, you may need to access the unopened jdk internals. For these cases, consider fallback to the interface type (for example, switching List from ArrayListto) or providing the required -- add opens parameter.

18.5.9. Use document reference

Using@DocumentReference Provides a flexible way to reference entities in MongoDB. Although the target is the same as when using DBRefs, the storage representation is different. DBRefs resolve to documents with fixed structure, as described in the MongoDB reference document. Document references do not follow a specific format. They can actually be anything, a single value, an entire document, and basically all the contents stored in MongoDB. By default, the mapping layer will use the referenced entity id value for storage and retrieval, as shown in the following example.

@Document
class Account {

  @Id
  String id;
  Float total;
}

@Document
class Person {

  @Id
  String id;

  @DocumentReference                                   
  List<Account> accounts;
}
Account account = ...

tempate.insert(account);                               

template.update(Person.class)
  .matching(where("id").is(...))
  .apply(new Update().push("accounts").value(account)) 
  .first();
{
  "_id" : ...,
  "accounts" : [ "6509b9e" ... ]                        
}

Tag the collection of values to be referenced by Account.

The mapping framework does not handle cascading saves, so make sure to keep referenced entities separately.

Add a reference to an existing entity.

The referenced Account entity is represented as an array of its _id values.

The above example uses _idto retrieve data based on a fetch query ({'_id':? #{#target}}) and eagerly resolve linked entities. The resolution default (listed below) can be changed using the following properties @ DocumentReference

Delayed loading may require a class proxy. Conversely, since JEP 396: strongly encapsulated jdk internals by default, starting from Java 16 +, you may need to access the unopened jdk internals. For these cases, consider fallback to the interface type (for example, switching List from ArrayListto) or providing the required -- add opens parameter.

DocumentReference(lookup) allows you to define filter queries that may be different from the _idfield, so it provides a flexible way to define references between entities, as shown in the following example, where the of Publisher books is determined by its acronym rather than its internal id

@Document
class Book {

  @Id
  ObjectId id;
  String title;
  List<String> author;

  @Field("publisher_ac")
  @DocumentReference(lookup = "{ 'acronym' : ?#{#target} }") 
  Publisher publisher;
}

@Document
class Publisher {

  @Id
  ObjectId id;
  String acronym;                                            
  String name;

  @DocumentReference(lazy = true)                            
  List<Book> books;

}

Book document

{
  "_id" : 9a48e32,
  "title" : "The Warded Man",
  "author" : ["Peter V. Brett"],
  "publisher_ac" : "DR"
}

Publisher document

{
  "_id" : 1a23e45,
  "acronym" : "DR",
  "name" : "Del Rey",
  ...
}

Use this acronym field to query entities in the Publisher collection.

Delay loading references to the Book collection.

The above code snippet shows the reading aspect when using custom reference objects. Writing requires some additional settings because the mapping information does not express where #target comes from. The mapping layer requires the Converter to register a DocumentPointer between the target document and, as shown below:

@WritingConverter
class PublisherReferenceConverter implements Converter<Publisher, DocumentPointer<String>> {

	@Override
	public DocumentPointer<String> convert(Publisher source) {
		return () -> source.getAcronym();
	}
}

If DocumentPointer does not provide a converter, the target reference document can be calculated based on the given lookup query. In this case, the evaluation of the associated target property is shown in the following example.

@Document
class Book {

  @Id
  ObjectId id;
  String title;
  List<String> author;

  @DocumentReference(lookup = "{ 'acronym' : ?#{acc} }")  
  Publisher publisher;
}

@Document
class Publisher {

  @Id
  ObjectId id;
  String acronym;                                        
  String name;

  // ...
}
{
  "_id" : 9a48e32,
  "title" : "The Warded Man",
  "author" : ["Peter V. Brett"],
  "publisher" : {
    "acc" : "DOC"
  }
}

Use this acronym field to query entities in the Publisher collection.

Placeholders for field values of lookup queries, such as acc, are used to form reference documents.

It can also refer to a combination of @ ReadonlyProperty and @ DocumentReference for one to many uses of model relationships. This method allows the link type to store the link value not in the owning document, but in the reference document, as shown in the following example.

@Document
class Book {

  @Id
  ObjectId id;
  String title;
  List<String> author;

  ObjectId publisherId;                                        
}

@Document
class Publisher {

  @Id
  ObjectId id;
  String acronym;
  String name;

  @ReadOnlyProperty                                            
  @DocumentReference(lookup="{'publisherId':?#{#self._id} }")  
  List<Book> books;
}

Book document

{
  "_id" : 9a48e32,
  "title" : "The Warded Man",
  "author" : ["Peter V. Brett"],
  "publisherId" : 8cfb002
}

Publisher document

{
  "_id" : 8cfb002,
  "acronym" : "DR",
  "name" : "Del Rey"
}

Set the link from (Reference) to publisher (owner) by storing in the document Book. Publisher.idBook

Mark the property that holds the reference as read-only. This prevents Book from storing references to individuals in Publisher documents.

Use this #self variable to access the values in the Publisher document, and Books uses the matching publisherId in this retrieval

With all the above, you can model all types of associations between entities. See the non exhaustive list of examples below for possible scenarios.

Example 194. Simple document reference using id field

class Entity {
  @DocumentReference
  ReferencedObject ref;
}
// entity
{
  "_id" : "8cfb002",
  "ref" : "9a48e32" 
}

// referenced object
{
  "_id" : "9a48e32" 
}

MongoDB simple type can be used directly without further configuration.

Example 195. Using a simple document reference with an id field of an explicit lookup query

class Entity {
  @DocumentReference(lookup = "{ '_id' : '?#{#target}' }") 
  ReferencedObject ref;
}
// entity
{
  "_id" : "8cfb002",
  "ref" : "9a48e32"                                        
}

// referenced object
{
  "_id" : "9a48e32"
}

target defines the reference value itself.

Example 196. Document reference extraction refKey lookup query field

class Entity {
  @DocumentReference(lookup = "{ '_id' : '?#{refKey}' }")   
  private ReferencedObject ref;
}
@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
	public DocumentPointer<Document> convert(ReferencedObject source) {
		return () -> new Document("refKey", source.id);    
	}
}
// entity
{
  "_id" : "8cfb002",
  "ref" : {
    "refKey" : "9a48e32"                                   
  }
}

// referenced object
{
  "_id" : "9a48e32"
}

The key used to obtain the reference value must be the key used when writing.

refKey is the abbreviation of target.refKey.

Example 197. Document references with multiple values form a lookup query

class Entity {
  @DocumentReference(lookup = "{ 'firstname' : '?#{fn}', 'lastname' : '?#{ln}' }")  
  ReferencedObject ref;
}
// entity
{
  "_id" : "8cfb002",
  "ref" : {
    "fn" : "Josh",           
    "ln" : "Long"            
  }
}

// referenced object
{
  "_id" : "9a48e32",
  "firsntame" : "Josh",      
  "lastname" : "Long",       
}

Read / WIRTE keys fn and ln from / to linked files based on lookup queries.

Use a non id field to find the target document.

Example 198. Reading a document reference from the target collection

class Entity {
  @DocumentReference(lookup = "{ '_id' : '?#{id}' }", collection = "?#{collection}") 
  private ReferencedObject ref;
}
@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
	public DocumentPointer<Document> convert(ReferencedObject source) {
		return () -> new Document("id", source.id)                                   
                           .append("collection", ... );                                
	}
}
// entity
{
  "_id" : "8cfb002",
  "ref" : {
    "id" : "9a48e32",                                                                
    "collection" : "..."                                                               
  }
}

_ id reads / writes keys from / to the reference document to use them in lookup queries.

You can use its key to read the collection name from the reference document.

We know that it is tempting to use various MongoDB query operators in lookup queries, which is good. However, there are several aspects to consider:

Make sure you have an index that supports your search.

Please note that the server round trip is required for resolution, resulting in delay. Please consider using inert strategy.

Use the $or operator to bulk load a collection of document references.

Try your best to restore the original element order in memory. The order can be restored only when using an equality expression, but not when using the MongoDB query operator. In this case, the results are sorted when received from the store or through the @ DocumentReference(sort) attribute provided.

Some more general comments:

Do you use circular references? Ask yourself if you need them.

Lazy document references are difficult to debug. Ensure that the tool does not accidentally trigger proxy resolution, such as calling toString()

Reading document references using reactive infrastructure is not supported.

18.5.10. Mapping framework events

Events are triggered throughout the lifecycle of the mapping process. This is described in the lifecycle Events section.

Declaring these bean s in the Spring ApplicationContext causes them to be called when scheduling events.

18.6. Deployment type

Unpacking entities are used to design value objects in the Java domain model, and their properties are flattened into the parent MongoDB document.

18.6.1. Expand type mapping

Consider that the following User.name uses @ Unwrapped. The @ Unwrapped annotation signal is that all attributes UserName should be flattened out until user owns the document name attribute.

Example 199. Example code of unpacking object

class User {

    @Id
    String userId;

    @Unwrapped(onEmpty = USE_NULL) 
    UserName name;
}

class UserName {

    String firstname;

    String lastname;

}
{
  "_id" : "1da2ba06-3ba7",
  "firstname" : "Emma",
  "lastname" : "Frost"
}

When loading the name attribute, its value is set to null if both firstname and lastname are either null or do not exist. By using onEmpty=USE_EMPTY an empty UserName, null the potential value of its attribute, will be created.

For less verbose embeddable type declarations, use @ unwrapped Nullableand@Unwrapped.Empty Replace @ Unwrapped(onEmpty = USE_NULL)and @Unwrapped(onEmpty = USE_EMPTY). Both annotations use JSR-305@javax.annotation.Nonnull Make meta comments to help with nullability checking.

You can use complex types in expanded objects. However, those cannot be or contain the unpacked field itself.

18.6.2. Unpacking type field name

By using the optional prefix attribute of the annotation, a value object can be unpacked @ Unwrapped multiple times. By adding, the selected prefix is added before each attribute or name in the @ Field("...") unpacking object. Note that if multiple properties are rendered with the same field name, the values will overwrite each other.

Example 200. Example code of unpacking object with name prefix

class User {

    @Id
    String userId;

    @Unwrapped.Nullable(prefix = "u_") 
    UserName name;

    @Unwrapped.Nullable(prefix = "a_") 
    UserName name;
}

class UserName {

    String firstname;

    String lastname;
}
{
  "_id" : "a6a805bd-f95f",
  "u_firstname" : "Jean",             
  "u_lastname" : "Grey",
  "a_firstname" : "Something",        
  "a_lastname" : "Else"
}

All properties of UserName are prefixed u_.

All properties of UserName are prefixed with a_.

Although it makes no sense to combine the @ Field annotation with the same attribute as @ Unwrapped, it will cause an error. This is a completely valid method for any Unwrapped type attribute of @ Field.

Example 201. Example code for unlocking objects using @ Field annotation

public class User {

	@Id
    private String userId;

    @Unwrapped.Nullable(prefix = "u-") 
    UserName name;
}

public class UserName {

	@Field("first-name")              
    private String firstname;

	@Field("last-name")
    private String lastname;
}
{
  "_id" : "2647f7b9-89da",
  "u-first-name" : "Barbara",         
  "u-last-name" : "Gordon"
}

All properties of UserName are prefixed u-.

The final field name is the result of connecting @ Unwrapped(prefix) and @ Field(name).

18.6.3. Query unpacking object

Queries on unwrapped attributes can be defined at the type and field levels because the Criteria content provided matches the domain type. Prefixes and potential custom field names are considered when rendering the actual query. Match all contained fields with the property name of the unpacked object, as shown in the following example.

Example 202. Query unpacking object

UserName userName = new UserName("Carol", "Danvers")
Query findByUserName = query(where("name").is(userName));
User user = template.findOne(findByUserName, User.class);
db.collection.find({
  "firstname" : "Carol",
  "lastname" : "Danvers"
})

You can also directly use its attribute name to address any field of the unpacked object, as shown in the following code snippet.

Example 203. Query fields of unpackaged objects

Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
List<User> users = template.findAll(findByUserFirstName, User.class);
db.collection.find({
  "firstname" : "Shuri"
})

Sort by expanded field.

The fields of an expanded object can be used to sort through its attribute path, as shown in the following example.

Example 204. Sorting on expanded fields

Query findByUserLastName = query(where("name.lastname").is("Romanoff"));
List<User> user = template.findAll(findByUserName.withSort(Sort.by("name.firstname")), User.class);
db.collection.find({
  "lastname" : "Romanoff"
}).sort({ "firstname" : 1 })

Although possible, using the unpacked object itself as the sorting standard includes the unpredictable order of all its fields and may lead to inaccurate sorting.

Field projection on an expanded object

The field of an expanded object can be projected as a whole or through a single field, as shown in the following example.

Example 205. Project on an expanded object.

Query findByUserLastName = query(where("name.firstname").is("Gamora"));
findByUserLastName.fields().include("name");                             
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
  "lastname" : "Gamora"
},
{
  "firstname" : 1,
  "lastname" : 1
})

Expand the field projection on the object, including all its attributes.

Example 206. Project on a field of an expanded object.

Query findByUserLastName = query(where("name.lastname").is("Smoak"));
findByUserLastName.fields().include("name.firstname");                   
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
  "lastname" : "Smoak"
},
{
  "firstname" : 1
})

Expand the field projection on the object, including all its attributes.

Query by example on unwrapped objects.

The expanded object can be used in the probe like Example any other type. See the query by Example section for more information about this feature.

Repository query for unpacked objects.

This Repository abstraction allows you to export queries for fields of unwrapped objects and for the entire object.

Example 207. Repository query for unpacked objects.

interface UserRepository extends CrudRepository<User, String> {

	List<User> findByName(UserName username);         

	List<User> findByNameFirstname(String firstname); 
}

Matches all fields of the unpacked object.

With firstname

Creating indexes for unpacked objects pauses true even if the repository create query indexes namespace property is set to true.

18.6.4. Update of expanded object

The expanded object can be updated as any other object that is part of the domain model. The mapping layer is responsible for flattening the structure into its surroundings. You can update a single attribute and the entire value of the unpacked object, as shown in the following example.

Example 208. Update a single field of the unpacked object.

Update update = new Update().set("name.firstname", "Janet");
template.update(User.class).matching(where("id").is("Wasp"))
   .apply(update).first()
db.collection.update({
  "_id" : "Wasp"
},
{
  "$set" { "firstname" : "Janet" }
},
{ ... }
)

Example 209. Update an expanded object.

Update update = new Update().set("name", new Name("Janet", "van Dyne"));
template.update(User.class).matching(where("id").is("Wasp"))
   .apply(update).first()
db.collection.update({
  "_id" : "Wasp"
},
{
  "$set" {
    "firstname" : "Janet",
    "lastname" : "van Dyne",
  }
},
{ ... }
)

18.6.5. Aggregation on unpackaged objects

The aggregation framework attempts to map the expanded values of type aggregates. When referencing one of its values, be sure to use the property path that includes the wrapper object. In addition, no special operation is required.

18.6.6. Expand object index

You can attach the @ Indexed annotation to a property of the unpacked type, just as you do for a regular object. @ Indexed cannot be used with @ Unwrapped comments that have properties.

public class User {

	@Id
    private String userId;

    @Unwrapped(onEmpty = USE_NULL)
    UserName name;                    

    // Invalid -> InvalidDataAccessApiUsageException
    @Indexed                          
    @Unwrapped(onEmpty = USE_Empty)
    Address address;
}

public class UserName {

    private String firstname;

    @Indexed
    private String lastname;           
}

Index creates the users collection of lastname.

@Invalid @ Unwrapped when used with Indexed

18.7. Custom transformation - overwrite default mapping

The easiest way to affect the mapping results is to specify the required native MongoDB target type through the @ Field annotation. This allows BigDecimal to use non MongoDB types in the domain model while persisting values in the native org.bson.types.Decimal128 format.

Example 210. Explicit target type mapping

public class Payment {

  @Id String id; 

  @Field(targetType = FieldType.DECIMAL128) 
  BigDecimal value;

  Date date; 

}
{
  "_id"   : ObjectId("5ca4a34fa264a01503b36af8"), 
  "value" : NumberDecimal(2.099), 
  "date"   : ISODate("2019-04-03T12:11:01.870Z") 
}

Indicates a valid string id value. ObjectId is automatically converted. of

For more information, see how to_ id processes fields in the mapping layer.

The required target type is explicitly defined as Decimal128 and converted to NumberDecimal. Otherwise, the

The BigDecimal value will be adjusted to String

The Date value is processed by the MongoDB driver itself and stored as ISODate

The code snippet above is convenient for providing simple type hints. For finer grained control of the mapping process, you can register a Spring converter using the MongoConverter implementation, such as MappingMongoConverter

The MappingMongoConverter checks whether any Spring converters can handle specific classes before attempting to map the object itself. To "hijack" the normal mapping policy MappingMongoConverter, perhaps in order to improve performance or other custom mapping requirements, you first need to create a Spring implementation Converter interface, and then use it to register MappingConverter.

For more information about Spring type conversion services, see the reference documentation here.

Posted by Tagette on Mon, 29 Nov 2021 08:18:28 -0800