[[elasticsearch.mapping]] = Elasticsearch Object Mapping Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back. Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <>. As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used. The main reasons for the removal of the Jackson based mapper are: * Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`. This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API. * Custom field types and formats also need to be stored into the Elasticsearch index mappings. The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch. * Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places. Using the `MappingElasticsearchConverter` now covers all these cases. [[elasticsearch.mapping.meta-model]] == Meta Model Object Mapping The Metamodel based approach uses domain type information for reading/writing from/to Elasticsearch. This allows to register `Converter` instances for specific domain type mapping. [[elasticsearch.mapping.meta-model.annotations]] === Mapping Annotation Overview The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents. The metadata is taken from the entity's properties which can be annotated. The following annotations are available: * `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. The most important attributes are: ** `indexName`: the name of the index to store this entity in. This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"` ** `type`: [line-through]#the mapping type. If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0) ** `shards`: the number of shards for the index. ** `replicas`: the number of replicas for the index. ** `refreshIntervall`: Refresh interval for the index. Used for index creation. Default value is _"1s"_. ** `indexStoreType`: Index storage type for the index. Used for index creation. Default value is _"fs"_. ** `createIndex`: flag whether to create an index on repository bootstrapping. Default value is _true_. See <> ** `versionType`: Configuration of version management. Default value is _EXTERNAL_. * `@Id`: Applied at the field level to mark the field used for identity purpose. * `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field. * `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document. * `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference): ** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used. ** `type`: the field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types] ** `format` and `pattern` definitions for the _Date_ type. `format` must be defined for date types. ** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_. ** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer. * `@GeoPoint`: marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class. NOTE: Properties that derive from `TemporalAccessor` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registered for this type. + If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7]. The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic. [[elasticsearch.mapping.meta-model.rules]] === Mapping Rules ==== Type Hints Mapping uses _type hints_ embedded in the document sent to the server to allow generic type mapping. Those type hints are represented as `_class` attributes within the document and are written for each aggregate root. .Type Hints ==== [source,java] ---- public class Person { <1> @Id String id; String firstname; String lastname; } ---- [source,json] ---- { "_class" : "com.example.Person", <1> "id" : "cb7bef", "firstname" : "Sarah", "lastname" : "Connor" } ---- <1> By default the domain types class name is used for the type hint. ==== Type hints can be configured to hold custom information. Use the `@TypeAlias` annotation to do so. NOTE: Make sure to add types with `@TypeAlias` to the initial entity set (`AbstractElasticsearchConfiguration#getInitialEntitySet`) to already have entity information available when first reading data from the store. .Type Hints with Alias ==== [source,java] ---- @TypeAlias("human") <1> public class Person { @Id String id; // ... } ---- [source,json] ---- { "_class" : "human", <1> "id" : ... } ---- <1> The configured alias is used when writing the entity. ==== NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the actual value type does not match the properties declaration. ==== Geospatial Types Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs. .Geospatial types ==== [source,java] ---- public class Address { String city, street; Point location; } ---- [source,json] ---- { "city" : "Los Angeles", "street" : "2800 East Observatory Road", "location" : { "lat" : 34.118347, "lon" : -118.3026284 } } ---- ==== ==== GeoJson Types Spring Data Elasticsearch supports the GeoJson types by providing an interface `GeoJson` and implementations for the different geometries. They are mapped to Elasticsearch documents according to the GeoJson specification. The corresponding properties of the entity are specified as `geo_shape` when the index mappings is written. .GeoJson types ==== [source,java] ---- public class Address { String city, street; GeoJsonPoint location; } ---- [source,json] ---- { "city": "Los Angeles", "street": "2800 East Observatory Road", "location": { "type": "Point", "coordinates": [-118.3026284, 34.118347] } } ---- ==== The following GeoJson types are implemented: * `GeoJsonPoint` * `GeoJsonMultiPoint` * `GeoJsonLineString` * `GeoJsonMultiLineString` * `GeoJsonPolygon` * `GeoJsonMultiPolygon` * `GeoJsonGeometryCollection` ==== Collections For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <>. .Collections ==== [source,java] ---- public class Person { // ... List friends; } ---- [source,json] ---- { // ... "friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ] } ---- ==== ==== Maps For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <>. However the Map key needs to a String to be processed by Elasticsearch. .Collections ==== [source,java] ---- public class Person { // ... Map knownLocations; } ---- [source,json] ---- { // ... "knownLocations" : { "arrivedAt" : { "city" : "Los Angeles", "street" : "2800 East Observatory Road", "location" : { "lat" : 34.118347, "lon" : -118.3026284 } } } } ---- ==== [[elasticsearch.mapping.meta-model.conversions]] === Custom Conversions Looking at the `Configuration` from the <> `ElasticsearchCustomConversions` allows registering specific rules for mapping domain and simple types. .Meta Model Object Mapping Configuration ==== [source,java] ---- @Configuration public class Config extends AbstractElasticsearchConfiguration { @Override public RestHighLevelClient elasticsearchClient() { return RestClients.create(ClientConfiguration.create("localhost:9200")).rest(); } @Bean @Override public ElasticsearchCustomConversions elasticsearchCustomConversions() { return new ElasticsearchCustomConversions( Arrays.asList(new AddressToMap(), new MapToAddress())); <1> } @WritingConverter <2> static class AddressToMap implements Converter> { @Override public Map convert(Address source) { LinkedHashMap target = new LinkedHashMap<>(); target.put("ciudad", source.getCity()); // ... return target; } } @ReadingConverter <3> static class MapToAddress implements Converter, Address> { @Override public Address convert(Map source) { // ... return address; } } } ---- [source,json] ---- { "ciudad" : "Los Angeles", "calle" : "2800 East Observatory Road", "localidad" : { "lat" : 34.118347, "lon" : -118.3026284 } } ---- <1> Add `Converter` implementations. <2> Set up the `Converter` used for writing `DomainType` to Elasticsearch. <3> Set up the `Converter` used for reading `DomainType` from search result. ====