Using Instant, LocalDateTime and ZonedDateTime with Spring Boot and ElasticSearch
Managed to get it to work with Spring Boot 2.1.4 and Spring Data Jest. Here is what I did:
Example domain object:
@Document(indexName = "datetest")public class DateTest { @Id private String id; @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ", timezone = "UTC") private Instant instant = Instant.now(); @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ") private ZonedDateTime zonedDateTime = ZonedDateTime.now(); @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSS") private LocalDateTime localDateTime = LocalDateTime.now(); // getters/setters}
The ElasticSearch/JEST config:
@Configurationpublic class ESConfig { @Bean public EntityMapper getEntityMapper() { return new CustomEntityMapper(); } @Bean @Primary public ElasticsearchOperations elasticsearchTemplate(final JestClient jestClient, final ElasticsearchConverter elasticsearchConverter, final SimpleElasticsearchMappingContext simpleElasticsearchMappingContext, EntityMapper mapper) { return new JestElasticsearchTemplate(jestClient, elasticsearchConverter, new DefaultJestResultsMapper(simpleElasticsearchMappingContext, mapper)); } public class CustomEntityMapper implements EntityMapper { private final ObjectMapper objectMapper; public CustomEntityMapper() { objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); objectMapper.registerModule(new CustomGeoModule()); objectMapper.registerModule(new JavaTimeModule()); } @Override public String mapToString(Object object) throws IOException { return objectMapper.writeValueAsString(object); } @Override public <T> T mapToObject(String source, Class<T> clazz) throws IOException { return objectMapper.readValue(source, clazz); } }}
The results in ElasticSearch:
Hope this helps.
That's because spring-data-jest
uses DefaultEntityMapper
(part of Spring Data), which creates its own ObjectMapper
and doesn't use the one provided by Spring boot. This can be seen in this related question.
You're on the right track with your solution by defining your own EntityMapper
, for example CustomEntityMapper
. However, spring-data-jest
wraps this mapper into a class called DefaultJestResultsMapper
, which is then used by a bean called JestElasticsearchTemplate
.
So, probably you should do something like this:
@Beanpublic JestResultsMapper resultMapper(CustomEntityMapper entityMapper) { return new DefaultJestResultsMapper(entityMapper);}@Beanpublic JestElasticSearchTemplate template(JestClient client, JestResultsMapper resultsMapper) { return new JestElasticSearchTemplate(client, resultsMapper);}
This should inject your CustomEntityMapper
into a JestResultsMapper
, which is in turn injected into JestElasticSearchTemplate
used by the framework.
Within CustomEntityMapper
you can either autowire the default ObjectMapper
(which will automatically add the JavaTimeModule
) or you can configure one on your own.
According to this answer from version 2 of Spring Boot,it should work out of the box as you want in terms of producing string from java.time objects
If you have
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.4.0
as dependency and below line in application.properties
spring.jackson.serialization.write_dates_as_timestamps=false
So only thing left would be to add timezone notation to default string which won't have it.
If standard formatters won't work for you may always write your own serialiser/deserialiser and attach it like explained here