Original title: Spring certified China Education Management Center - Spring Data MongoDB tutorial 13 (content source: Spring China Education Management Center)
18.1.4.Kotlin support
Spring Data tweaks the details of Kotlin to allow objects to be created and changed.
Kotlin object creation
Kotlin class supports instantiation. By default, all classes are immutable, and explicit attribute declaration is required to define variable attributes. Consider the following data class Person:
data class Person(val id: String, val name: String)
The above class is compiled into a typical class with an explicit constructor. We can customize this class by adding another constructor and use the annotation @ PersistenceConstructor to indicate constructor preferences:
data class Person(var id: String, val name: String) { @PersistenceConstructor constructor(id: String) : this(id, "unknown") }
Kotlin supports parameter selectability by allowing default values to be used when no parameters are provided. When Spring Data detects a constructor with parameter default values, if the data store does not provide values (or simply returns null), it will make these parameters nonexistent, so kotlin can apply parameter default values. Consider the following class name that applies parameter default values
data class Person(var id: String, val name: String = "unknown")
Each time the name parameter is not part of the result or its value is null, the name defaults to unknown.
Property filling of Kotlin data class
In Kotlin, all classes are immutable by default, and explicit attribute declarations are required to define variable attributes. Consider the following data class Person:
data class Person(val id: String, val name: String)
This class is virtually immutable. It allows the creation of new instances because Kotlin generates a copy(...) method to create new object instances, which copies all property values from existing objects and applies the property values provided as parameters to the method.
Kotlin override properties
Kotlin allows you to declare attribute overrides to change attributes in subclasses.
open class SuperType(open var field: Int) class SubType(override var field: Int = 1) : SuperType(field) { }
This arrangement presents two property field s named. Kotlin generates property accessors (getter s and setter s) for each property in each class. In fact, the code is as follows:
public class SuperType { private int field; public SuperType(int field) { this.field = field; } public int getField() { return this.field; } public void setField(int field) { this.field = field; } } public final class SubType extends SuperType { private int field; public SubType(int field) { super(field); this.field = field; } public int getField() { return this.field; } public void setField(int field) { this.field = field; } }
getter and setterSubType are only on set, and SubType.field is not SuperType.field. In this arrangement, using constructor is the only default method for setting SuperType.field. Add method to SubTypeset Supertype.fieldviatis.supertype.field =... Is possible but not a supported convention. Property overrides can conflict to some extent because properties share the same name but may represent two different values. We usually recommend using different property names.
Spring Data modules usually support override attributes with different values. From the perspective of programming model, the following points need to be considered:
- Which attribute should be retained (all declared attributes by default)? You can exclude the attribute @ Transient by annotating these attributes.
- How to represent attributes in the data store? Using the same field / column name for different values usually leads to data corruption, so you should annotate at least one attribute with an explicit field / column name.
- using@AccessType(PROPERTY) cannot be used because super properties cannot be set.
18.2. Contract based mapping
MappingMongoConverter has some conventions for mapping objects to documents when no additional mapping metadata is provided. These conventions are:
- The short Java class name maps to the collection name in the following manner. The class com.bigbank.SavingsAccount maps to the savingsAccount collection name.
- All nested objects are stored in the document as nested objects, not as dbrefs.
- The converter uses any registered Spring converter to override the default mapping of object properties to document fields and values.
- Object is used to convert between fields in a document. JavaBean s do not use public properties.
- If you have a non-zero parameter constructor whose constructor parameter name matches the document's top-level field name, use the constructor. Otherwise, the zero parameter constructor will be used. If there are multiple non-zero parameter constructors, an exception will be thrown.
18.2.1. _idhow to process fields in the mapping layer.
MongoDB requires that you have a _idfield containing all documents. If you do not provide it, the driver will assign an ObjectId with the generated value. "_id" A field can be any type other than an array as long as it is unique. The driver naturally supports all original types and dates. When used, MappingMongoConverter has some rules that control how attributes in Java classes are mapped to this _idfield.
The following outlines the fields that will be mapped to _id document fields:
- Fields annotated with @ id (org. Springframework. Data. Annotation. id) are mapped to this _idfield.
- An _idfield with no comments but a named id is mapped to that field.
- The default Field name of the identifier is _idand can be customized through the @ Field annotation.
The following is an overview of the type conversions, if any, for attributes mapped to _id document fields.
- If Id declares a named field as String or BigInteger in a Java class, it will be converted to ObjectId and stored as ObjectId as far as possible. ObjectId is also valid as a field type. If you specify a value in the application, the MongoDB driver will detect the conversion of ObjectId. If the specified Id value cannot be converted to ObjectId, the value will be changed Will be stored as is in the _idfield of the document. If the field is annotated, this also applies to @ Id.
- If a field @ MongoId is annotated in a Java class, it will be converted to and stored as the actual type using it. No further conversion will occur unless @ MongoId declares the required field type.
- If a field @ MongoId(FieldType,...) is annotated in a Java class, it will attempt to convert the value to the declared FieldType
- If the field named idid field is not declared as String, BigInteger, or ObjectID in the Java class, you should assign it a value in the application so that it can be stored "as is" in the _idfield of the document.
- If the named field _iddoes not exist in the idJava class, the driver generates an implicit file, but does not map to the properties or fields of the Java class.
During Query and Update, MongoTemplate will use the converter to process the conversion of Query and Update objects corresponding to the above document saving rules, so the field names and types used in the Query will be able to match the contents in the domain class.
18.3. Data mapping and type conversion
This section explains how types are mapped to and from MongoDB representations. Spring Data MongoDB supports all types that can be represented as BSON (internal document format of MongoDB). In addition to these types, Spring Data MongoDB provides a set of built-in converters to map other types. You can provide your own converter to adjust the type conversion. For more details, see[ mapping-explicit-converters].
Examples of each of the available type conversions are provided below:
18.4. Mapping configuration
Unless explicitly configured, the MappingMongoConverter is creating a MongoTemplate. You can create your own MappingMongoConverter. This allows you to specify where domain classes can be found in the classpath so that Spring Data MongoDB can extract metadata and build indexes. In addition, by creating your own instance, you can register the Spring converter to map specific classes Map to or from a database.
You can use Java based or XML based metadata to configure MappingMongoConverter and com.mongodb.client.MongoClientMongoTemplate. The following example uses Spring's Java based configuration:
Example 180.@Configuration class to configure MongoDB mapping support
@Configuration public class MongoConfig extends AbstractMongoClientConfiguration { @Override public String getDatabaseName() { return "database"; } // the following are optional @Override public String getMappingBasePackage() { return "com.bigbank.domain"; } @Override void configureConverters(MongoConverterConfigurationAdapter adapter) { adapter.registerConverter(new org.springframework.data.mongodb.test.PersonReadConverter()); adapter.registerConverter(new org.springframework.data.mongodb.test.PersonWriteConverter()); } @Bean public LoggingEventListener<MongoMappingEvent> mappingEventsListener() { return new LoggingEventListener<MongoMappingEvent>(); } }
The mapping base package defines the MappingContext for scanning and pre initialization. By default, the configuration class package is used.
Configure additional custom converters for specific domain types and replace the default mapping process for these types with your custom implementation.
AbstractMongoClientConfiguration requires you to implement methods that define acom.mongodb.client.MongoClient and provide database names. AbstractMongoClientConfiguration also has a method named getMappingBasePackage(...), which you can override to tell the converter where to scan classes annotated with @ Document annotations.
You can override the The customConversionsConfiguration method adds other converters to the converter. The native JSR-310 support of MongoDB can be through mongoconverterconfigurationadapter. Usenativedriverjavatimecodes(). The previous example also shows a LoggingEventListener, which records the instance of MongoMappingEvent published to the SpringApplicationContextEvent infrastructure.
AbstractMongoClientConfiguration creates a mongotemplate instance and registers it with the container named mongotemplate.
Spring's MongoDB namespace allows you to enable mapping in XML, as shown in the following example:
Example 181. Configure the XML schema supported by MongoDB mapping
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xsi:schemaLocation=" http://www.springframework.org/schema/data/mongo https://www.springframework.org/schema/data/mongo/spring-mongo.xsd http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- Default bean name is 'mongo' --> <mongo:mongo-client host="localhost" port="27017"/> <mongo:db-factory dbname="database" mongo-ref="mongoClient"/> <!-- by default look for a Mongo object named 'mongo' - default name used for the converter is 'mappingConverter' --> <mongo:mapping-converter base-package="com.bigbank.domain"> <mongo:custom-converters> <mongo:converter ref="readConverter"/> <mongo:converter> <bean class="org.springframework.data.mongodb.test.PersonWriteConverter"/> </mongo:converter> </mongo:custom-converters> </mongo:mapping-converter> <bean id="readConverter" class="org.springframework.data.mongodb.test.PersonReadConverter"/> <!-- set the mapping converter to be used by the MongoTemplate --> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/> <constructor-arg name="mongoConverter" ref="mappingConverter"/> </bean> <bean class="org.springframework.data.mongodb.core.mapping.event.LoggingEventListener"/> </beans>
The base package attribute tells it where to scan@ org.springframework.data.mongodb.core.mapping.Document annotated class.
18.5. Metadata based mapping
To take full advantage of the object mapping function in Spring Data MongoDB support, you should annotate the mapped object with @ Document annotation. Although the mapping framework does not need to have this annotation (your POJO is mapped correctly, even if there is no annotation) , but it allows the classpath scanner to find and preprocess your domain objects to extract the necessary metadata. If you do not use this annotation, your application will suffer a slight performance impact when you store domain objects for the first time, because the mapping framework needs to establish its internal metadata model so that it knows the properties of your domain objects and how to adhere to them. As shown below Example shows a domain object:
Example 182. Example domain object
package com.mycompany.domain; @Document public class Person { @Id private ObjectId id; @Indexed private Integer ssn; private String firstName; @Indexed private String lastName; }
The @ id annotation tells you which attribute mapper of MongoDB to use. The _idattribute and the @ Indexed annotation tell the mapping framework to call createIndex(...) the attribute of your Document, making the search faster. Automatic index creation is only applicable to @ Document
Automatic index creation is disabled by default and needs to be enabled by configuration (see index creation).
18.5.1. Index creation
Spring Data MongoDB can automatically use @ Document. Since version 3.0, index creation must be explicitly enabled to prevent adverse impact on collection life cycle and performance. The index is automatically created for the initial entity set when the application starts and when the entity type is accessed for the first time when the application runs.
We usually recommend explicitly creating indexes for application based index control because Spring Data cannot automatically create indexes for collections recreated at application runtime.
IndexResolver if you want to use @ Indexed annotations such as @ GeoSpatialIndexed, @TextIndexed, it provides an abstract @ CompoundIndex for programmatic index definition creation. You can use index definition IndexOperations to create an index. A good time to create an index is to trigger contextr by observation when the application starts, especially after the application context is refreshed Efreshedevent. This event ensures that the context has been fully initialized. Note that other components, especially bean factories, may have access to the MongoDB database at this time.
Example 183. Programmatic index creation for a single domain type
class MyListener { @EventListener(ContextRefreshedEvent.class) public void initIndicesAfterStartup() { MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext = mongoTemplate .getConverter().getMappingContext(); IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext); IndexOperations indexOps = mongoTemplate.indexOps(DomainType.class); resolver.resolveIndexFor(DomainType.class).forEach(indexOps::ensureIndex); } }
Example 184. Create program indexes for all initial entities
class MyListener{ @EventListener(ContextRefreshedEvent.class) public void initIndicesAfterStartup() { MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext = mongoTemplate .getConverter().getMappingContext(); // consider only entities that are annotated with @Document mappingContext.getPersistentEntities() .stream() .filter(it -> it.isAnnotationPresent(Document.class)) .forEach(it -> { IndexOperations indexOps = mongoTemplate.indexOps(it.getType()); resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex); }); } }
Or, if you want to ensure the existence of indexes and collections before any component can access your database from your application, declare a @ Bean method MongoTemplate and include the above code MongoTemplate before returning the object.
To turn off automatic index creation ON, override autoIndexCreation() in your configuration.
@Configuration public class Config extends AbstractMongoClientConfiguration { @Override public boolean autoIndexCreation() { return true; } // ... }
Since version 3.0, automatic index creation is turned off by default.
18.5.2. Overview of mapping notes
MappingMongoConverter can use metadata to drive object to document mapping. The following comments are available:
- @Id: applied at the field level to mark the field for identification purposes.
- @MongoId: applied at the field level to mark the field for identification purposes. Accepts a custom id conversion of an optional FieldType.
- @Document: applies to the class level, indicating that the class is a candidate for mapping to the database. You can specify the name of the collection where the data will be stored.
- @DBRef: applies to this field to indicate that it will be stored using com.mongodb.DBRef.
- @DocumentReference: applied to this field to indicate that it will be stored as a pointer to another Document. This can be a single value (id by default) or a value provided by the Document through the converter.
- @Indexed: applied at the field level. Describes how to index fields.
- @Compound index (repeatable): applied at the type level to declare a compound index.
- @GeoSpatialIndexed: applied at the field level to describe how fields are geo indexed.
- @TextIndexed: applied at the field level to mark the fields to be included in the text index.
- @HashIndexed: applied at the field level for use in hash indexes to partition data across fragmented clusters.
- @Language: applies at the field level to set the language override property of the text index.
- @Transient: by default, all fields are mapped to the document. This comment excludes the fields to which it applies from the database. Transient properties cannot be used in a persistence constructor because the converter cannot implement the value of the constructor parameter.
- @PersistenceConstructor: marks that the given constructor - even a package protected constructor - is used when instantiating objects from the database. Constructor parameters map by name to key values in the retrieved document.
- @Value: this annotation is part of the Spring Framework. Within the mapping framework, it can be applied to constructor parameters. This allows you to use Spring expression language statements to transform the key values retrieved in the database, and then use it to construct domain objects. In order to reference the properties of a given document, you must use the following expression: @ value ("#root. Myproperty") where root refers to the root of the given document.
- @Field: applied at the field level, it allows to describe the name and type of the field, because it will be represented in the MongoDB BSON document, which allows the name and type to be different from the field name and attribute type of the class.
- @Version: applied at the field level for optimistic locking and checking for changes to the save operation. The initial value is zero (one for the original type), which is automatically triggered every time it is updated.
The mapping metadata infrastructure is defined in a separate spring data commons project, which is technology independent. Specific subclasses are used in MongoDB support to support annotation based metadata. If necessary, other strategies can also be adopted.
This is a more complex mapping example.
@Document @CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}") public class Person<T extends Address> { @Id private String id; @Indexed(unique = true) private Integer ssn; @Field("fName") private String firstName; @Indexed private String lastName; private Integer age; @Transient private Integer accountTotal; @DBRef private List<Account> accounts; private T address; public Person(Integer ssn) { this.ssn = ssn; } @PersistenceConstructor public Person(Integer ssn, String firstName, String lastName, Integer age, T address) { this.ssn = ssn; this.firstName = firstName; this.lastName = lastName; this.age = age; this.address = address; } public String getId() { return id; } // no setter for Id. (getter is only exposed for some unit testing) public Integer getSsn() { return ssn; } // other getters/setters omitted }
@Field(targetType =...) can be used when the native MongoDB type inferred by the mapping infrastructure does not match the expected type. Like for BigDecimal, it is represented as String instead of Decimal128, just because it is not supported by earlier versions of MongoDB Server.
public class Balance {
@Field(targetType = DECIMAL128)
private BigDecimal value;
// ...
}
You can even consider your own custom comments.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Field(targetType = FieldType.DECIMAL128)
public @interface Decimal128 { }
// ...
public class Balance {
@Decimal128
private BigDecimal value;
// ...
}
18.5.3. Custom object construction
The mapping subsystem allows you to customize the object construction @ PersistenceConstructor by annotating the constructor with annotations. The values used for constructor parameters are resolved as follows:
- If the parameter is annotated with @ Value annotation, the given expression is evaluated and the result is used as the parameter Value.
- If the Java type has a property whose name matches the given field of the input document, use its property information to select the appropriate constructor parameter to pass the input field value to. This is only valid if there is parameter name information in the java.class file, which can be achieved by compiling the source code with debugging information or using the new command line switch of javac in - parametersJava 8.
- Otherwise, MappingException will throw a indicating that the given constructor parameter cannot be bound.
class OrderItem { private @Id String id; private int quantity; private double unitPrice; OrderItem(String id, @Value("#root.qty ?: 0") int quantity, double unitPrice) { this.id = id; this.quantity = quantity; this.unitPrice = unitPrice; } // getters/setters ommitted } Document input = new Document("id", "4711"); input.put("unitPrice", 2.5); input.put("qty",5); OrderItem item = converter.read(OrderItem.class, input);
If the given property path @ Value cannot be resolved, the spiel expression in the quantity Parameter annotation will fall back to the Value 0.
@PersistenceConstructor can Additional examples of using annotations can be found in the mappingmongoconverterinunittests test suite.
18.5.4. Composite index
Composite indexes are also supported. They are defined at the class level rather than on a single attribute.
Composite index is very important to improve the performance of queries involving multiple field conditions
This is an example of lastName creating a composite index in ascending and descending order:
Example 185. Composite index usage
package com.mycompany.domain; @Document @CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}") public class Person { @Id private ObjectId id; private Integer age; private String firstName; private String lastName; }
@CompoundIndexes can reuse @ CompoundIndexes as its container.
@Document @CompoundIndex(name = "cmp-idx-one", def = "{'firstname': 1, 'lastname': -1}") @CompoundIndex(name = "cmp-idx-two", def = "{'address.city': -1, 'address.street': 1}") public class Person { String firstname; String lastname; Address address; // ... }
18.5.5. Hash index
Hash index allows hash based sharding in a sharded cluster. Sharding a collection with hash field values results in a more random distribution. For details, see the MongoDB documentation.
The following is an example of creating a hash index_ id:
Example 186. Hash index usage example
@Document public class DomainType { @HashIndexed @Id String id; // ... }
Hash indexes can be created next to other index definitions, as shown below. In this case, both indexes are created:
Example 187. Example of hash index used with simple index
@Document public class DomainType { @Indexed @HashIndexed String value; // ... }
If the above example is too verbose, compound annotations allow you to reduce the number of annotations that need to be declared on attributes:
Example 188. Use example of combined hash index
@Document public class DomainType { @IndexAndHash(name = "idx...") String value; // ... } @Indexed @HashIndexed @Retention(RetentionPolicy.RUNTIME) public @interface IndexAndHash { @AliasFor(annotation = Indexed.class, attribute = "name") String name() default ""; }
Aliases may be registered for some properties of meta annotations.
Although creating an index by annotation is useful in many scenarios, consider taking over more control by manually setting the index.
mongoOperations.indexOpsFor(Jedi.class) .ensureIndex(HashedIndex.hashed("useTheForce"));