2019-01-17 10:52:32 +01:00
[[elasticsearch.mapping]]
= Elasticsearch Object Mapping
Spring Data Elasticsearch allows to choose between two mapping implementations abstracted via the `EntityMapper` interface:
* <<elasticsearch.mapping.jackson2>>
* <<elasticsearch.mapping.meta-model>>
[[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`.
====
2019-06-28 21:40:59 +02:00
[WARNING]
`CustomConversions`, `@ReadingConverter` & `@WritingConverter` cannot be applied when using the Jackson based `EntityMapper`. +
Setting the name of a mapped field with `@Field(name="custom-name")` also cannot be used with this Mapper.
2019-01-17 10:52:32 +01:00
[[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
2019-06-28 21:40:59 +02:00
public EntityMapper entityMapper() { <1>
2019-01-17 10:52:32 +01:00
2019-06-28 21:40:59 +02:00
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService() <2>
);
entityMapper.setConversions(elasticsearchCustomConversions()); <3>
2019-01-17 10:52:32 +01:00
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.
====
2019-02-07 13:34:31 +01:00
[[elasticsearch.mapping.meta-model.annotations]]
=== Mapping Annotation Overview
The `ElasticsearchEntityMapper` can use metadata to drive the mapping of objects to documents. The following annotations are available:
* `@Id`: Applied at the field level to mark the field used for identity purpose.
2019-06-28 21:40:59 +02:00
* `@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
** `type`: the mapping type. If not set, the lowercased simple name of the class is used.
** `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`: Configuration whether to create an index on repository bootstrapping. Default value is _true_.
** `versionType`: Configuration of version management. Default value is _EXTERNAL_.
2019-02-07 13:34:31 +01:00
* `@Transient`: By default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database
* `@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.
2019-06-28 21:40:59 +02:00
* `@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:
** `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, Integer, Long, Date, Float, Double, Boolean, Object, Auto, Nested, Ip, Attachment, Keyword_.
** `format` and `pattern` custom definitions for the _Date_ type.
** `store`: Flag wether the original field value should be store in Elasticsearch, default value is _false_.
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom custom analyzers and normalizer.
** `copy_to`: the target field to copy multiple document fields to.
* `@GeoPoint`: marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class.
2019-02-07 13:34:31 +01:00
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
2019-01-17 10:52:32 +01:00
[[elasticsearch.mapping.meta-model.rules]]
=== Mapping Rules
==== Type Hints
2019-02-07 13:34:31 +01:00
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.
2019-01-17 10:52:32 +01:00
.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.
====
2019-02-07 13:34:31 +01:00
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.
2019-01-17 10:52:32 +01:00
==== 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
2019-02-07 13:34:31 +01:00
For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
2019-01-17 10:52:32 +01:00
.Collections
====
[source,java]
----
public class Person {
// ...
List<Person> friends;
}
----
[source,json]
----
{
// ...
"friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ]
}
----
====
==== Maps
2019-02-07 13:34:31 +01:00
For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
2019-01-17 10:52:32 +01:00
However the Map key needs to a String to be processed by Elasticsearch.
.Collections
====
[source,java]
----
public class Person {
// ...
Map<String, Address> 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
2019-02-07 13:34:31 +01:00
Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, previous section>> `ElasticsearchCustomConversions` allows registering specific rules for mapping domain and simple types.
2019-01-17 10:52:32 +01:00
.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() {
2019-06-28 21:40:59 +02:00
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions()); <1>
2019-01-17 10:52:32 +01:00
return entityMapper;
}
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
2019-06-28 21:40:59 +02:00
return new ElasticsearchCustomConversions(
Arrays.asList(new AddressToMap(), new MapToAddress())); <2>
2019-01-17 10:52:32 +01:00
}
2019-06-28 21:40:59 +02:00
@WritingConverter <3>
2019-01-17 10:52:32 +01:00
static class AddressToMap implements Converter<Address, Map<String, Object>> {
@Override
public Map<String, Object> convert(Address source) {
LinkedHashMap<String, Object> target = new LinkedHashMap<>();
target.put("ciudad", source.getCity());
// ...
return target;
}
}
2019-06-28 21:40:59 +02:00
@ReadingConverter <4>
2019-01-17 10:52:32 +01:00
static class MapToAddress implements Converter<Map<String, Object>, Address> {
@Override
public Address convert(Map<String, Object> 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.
====