[[elasticsearch.mapping]] = Elasticsearch Object Mapping Spring Data Elasticsearch allows to choose between two mapping implementations abstracted via the `EntityMapper` interface: * <> * <> [[elasticsearch.mapping.jackson2]] == Jackson Object Mapping The Jackson2 based approach (used by default) utilizes a customized `ObjectMapper` instance with spring data specific modules. Extensions to the actual mapping need to be customized via Jackson annotations like `@JsonInclude`. .Jackson2 Object Mapping Configuration ==== [source,java] ---- @Configuration public class Config extends AbstractElasticsearchConfiguration { <1> @Override public RestHighLevelClient elasticsearchClient() { return RestClients.create(ClientConfiguration.create("localhost:9200")).rest() } } ---- <1> `AbstractElasticsearchConfiguration` already defines a Jackson2 based `entityMapper` via `ElasticsearchConfigurationSupport`. ==== WARNING: `CustomConversions`, `@ReadingConverter` & `@WritingConverter` cannot be applied when using the Jackson based `EntityMapper`. [[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. .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 EntityMapper entityMapper() { <1> ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService()); <2> entityMapper.setConversions(elasticsearchCustomConversions()); <3> return entityMapper; } } ---- <1> Overwrite the default `EntityMapper` from `ElasticsearchConfigurationSupport` and expose it as bean. <2> Use the provided `SimpleElasticsearchMappingContext` to avoid inconsistencies and provide a `GenericConversionService` for `Converter` registration. <3> Optionally set `CustomConversions` if applicable. ==== [[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 on the server and will be 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 } } ---- ==== ==== 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 to register 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 EntityMapper entityMapper() { ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService()); entityMapper.setConversions(elasticsearchCustomConversions()); <1> return entityMapper; } @Bean @Override public ElasticsearchCustomConversions elasticsearchCustomConversions() { return new ElasticsearchCustomConversions(Arrays.asList(new AddressToMap(), new MapToAddress())); <2> } @WritingConverter <3> static class AddressToMap implements Converter> { @Override public Map convert(Address source) { LinkedHashMap target = new LinkedHashMap<>(); target.put("ciudad", source.getCity()); // ... return target; } } @ReadingConverter <4> 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> Register `ElasticsearchCustomConversions` with the `EntityMapper`. <2> Add `Converter` implementations. <3> Set up the `Converter` used for writing `DomainType` to Elasticsearch. <4> Set up the `Converter` used for reading `DomainType` from search result. ====