前言:
以下解决方案均基于Spring boot 2.x Spring data elastic 3/4 以及 elastic search high level client 7.8。
报错:
No converter found capable of converting from type [java.lang.Long] to type [java.sql.Timestamp]
2021/09/01 更新(发现了一个更稳妥的解决方案)
解决方案0:
参考:
在配置类中增加 elasticsearchConverter
@Bean
ElasticsearchConverter elasticsearchConverter(SimpleElasticsearchMappingContext mappingContext) {
DefaultConversionService defaultConversionService = new DefaultConversionService();
defaultConversionService.addConverter(DateToLocalDateTimeConverter.INSTANCE);
defaultConversionService.addConverter(StringToLocalDateTimeConverter.INSTANCE);
defaultConversionService.addConverter(LongToLocalDateTimeConverter.INSTANCE);
defaultConversionService.addConverter(LongToTimestampConverter.INSTANCE);
defaultConversionService.addConverter(TimestampToLongConverter.INSTANCE);
return new MappingElasticsearchConverter(mappingContext, defaultConversionService);
}
然后注入 SimpleElasticsearchMappingContext
在新建esTemplate的时候,使用两个参数的构造器即可,贴一下构造器源码
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
Assert.notNull(client, "Client must not be null!");
this.client = client;
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
this.initialize(elasticsearchConverter);
}
解决方案1:
Spring data elastic search 版本 3.x,注意到4.x实际上没有ElasticsearchEntityMapper,全网搜到比较多的解决方案是基于这个版本。
@Configuration
public class ESConfiguration extends AbstractElasticsearchConfiguration {
@Value("${elasticsearch.config.hosts}")
private String hosts;
@Bean
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration configuration = ClientConfiguration.builder()
.connectedTo(this.hosts)
.build();
return RestClients.create(configuration).rest();
}
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService()
);
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
/**
* 默认的converter不支持long到localdatime的转换,从elasticsearch读取的时候会报错,所以在这里添加一个。
*/
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
List<Converter> converters= new ArrayList<>();
converters.add(LongToLocalDateTimeConverter.INSTANCE);
return new ElasticsearchCustomConversions(converters);
}
@ReadingConverter
static enum LongToLocalDateTimeConverter implements Converter<Long, LocalDateTime> {
INSTANCE;
private LongToLocalDateTimeConverter() {
}
public LocalDateTime convert(Long source) {
return Instant.ofEpochMilli(source).atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}
}
解决方案2:
基于 spring data elastic search 4.x
ConfigurationProperties(prefix = "elasticsearch") // 读取elasticsearch开头的配置
@Configuration
@Data
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
private String host;
private Integer port;
// 重写父类方法构建参数
@Override
public RestHighLevelClient elasticsearchClient() {
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
return restHighLevelClient;
}
@Bean
public ElasticsearchRestTemplate esRestTemplate() throws Exception {
return new ElasticsearchRestTemplate(elasticsearchClient());
}
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
List<Converter> converters= new ArrayList<>();
converters.add(DateToLocalDateTimeConverter.INSTANCE);
converters.add(StringToLocalDateTimeConverter.INSTANCE);
converters.add(LongToLocalDateTimeConverter.INSTANCE);
converters.add(LongToTimestampConverter.INSTANCE);
converters.add(TimestampToLongConverter.INSTANCE);
return new ElasticsearchCustomConversions(converters);
}
//格式化后保存结果为String类型
@ReadingConverter
enum StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
INSTANCE;
@Override
public java.time.LocalDateTime convert(String source) {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return LocalDateTime.parse(source,df);
}
}
@WritingConverter
enum DateToLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
INSTANCE;
@Override
public LocalDateTime convert(Date date) {
Instant instant = date.toInstant();
return instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}
//保存类型为long类型
@ReadingConverter
enum LongToLocalDateTimeConverter implements Converter<Long, LocalDateTime> {
INSTANCE;
@Override
public java.time.LocalDateTime convert(Long source) {
return Instant.ofEpochMilli(source).atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}
@ReadingConverter
enum LongToTimestampConverter implements Converter<Long, Timestamp> {
INSTANCE;
@Override
public java.sql.Timestamp convert(Long longTime) {
if(longTime != null){
return new Timestamp(longTime);
}
return null;
}
}
@ReadingConverter
enum TimestampToLongConverter implements Converter<Timestamp,Long> {
INSTANCE;
@Override
public Long convert(Timestamp source) {
return source.getTime();
}
}
// 4版本无法使用使用
// @Bean
// @Override
// public EntityMapper entityMapper() {
//
// ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
// elasticsearchMappingContext(), new DefaultConversionService()
// );
// entityMapper.setConversions(elasticsearchCustomConversions());
//
// return entityMapper;
// }
/**
* 默认的converter不支持
* long到localdatime的转换,
* long到TimeStamp的转换,
* 从elasticsearch读取的时候会报错,所以在这里添加一个。
*/
// @Bean
// @Override
// public ElasticsearchCustomConversions elasticsearchCustomConversions() {
// List<Converter> converters= new ArrayList<>();
// converters.add(LongToLocalDateTimeConverter.INSTANCE);
// converters.add(LongToTimeStampConverter.INSTANCE);
// return new ElasticsearchCustomConversions(converters);
// }
//
// @ReadingConverter
// @WritingConverter
// static enum LongToLocalDateTimeConverter implements Converter<Long, LocalDateTime> {
// INSTANCE;
//
// private LongToLocalDateTimeConverter() {
// }
//
// public LocalDateTime convert(Long source) {
// return Instant.ofEpochMilli(source).atZone(ZoneId.systemDefault()).toLocalDateTime();
// }
// }
// @ReadingConverter
// @WritingConverter
// static enum LongToTimeStampConverter implements Converter<Long, Timestamp> {
// INSTANCE;
// private LongToTimeStampConverter(){
//
// }
//
// @Override
// public Timestamp convert(Long longTime) {
// if(longTime != null){
// return new Timestamp(longTime);
// }
// return null;
// }
// }
}
解决方案3:
在存的时候就将数据格式化在存,不要存long <=> timestamp 本身就算读取到还得格式化,为啥不存的时候格式化好呢。
补充:
如果提示重复的Bean注入,看下这里,很多帖子都说custom converter的时候,会集成AbstractElasticsearchConfiguration,但是如果和你的high level rest template的配置在一起的话,看下冲突。
结语:
耗时比较长去解决这个小问题,最终是用方案3解决的,方案2遇到的问题是,converter注入了,但是在使用的时候,没有找到,真的有点浪费了。Spring data elastic search issue里边没有提到这个问题,目前也不知道4.1.x的版本是不是解决了,我的版本基于spring data elastic search 4.0.9.RELEASE,stack over flow 上有人提到这个问题,但是最后也是修改的格式,不晓得有没有其他方案,如果有路过的大佬,请指教。