当前位置:   article > 正文

Elastic实战:彻底解决spring-data-elasticsearch日期、时间类型数据读取报错问题_failed to convert from type [java.lang.string] to

failed to convert from type [java.lang.string] to type [@com.fasterxml.jacks

0. 引言

在使用spring-data-elasticsearch读取es中时间类型的数据时出现了日期转换报错,不少初学者会在这里困惑很久,所以今天我们专门来解读该问题的几种解决方案。

1. 问题分析

该问题的报错形式一般是:

Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2022-03-15T14:31:55+08:00'; 
nested exception is java.lang.IllegalArgumentException"
  • 1
  • 2

当然时间类型的表现形式不一定是我这里的2022-03-15T14:31:55+08:00,可能多种多样,但解决办法都是一致的。

该问题的原因很简单,就是es中存储的时间格式是该种类型的,使用java client去获取时,无法直接转换为时间类型

2. 问题解决

这里演示的环境版本是如下所示。如果在实操时发现部分代码不可用,注意检查版本

spring-data-elasticsearch3.2.12.RELEASE
  • 1

2.1 es mapping与实体类中声明相同的时间格式

第一种方案,是我们应该在实体类和索引mapping创建前就做好的,将es mapping中的时间字段的格式与实体类中保持统一
1、es mapping中设置时间格式,如果有多种格式中间用||隔开

PUT date_custom
{
  "mappings": {
    "properties": {
      "create_time": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd'T'HH:mm:ss'+08:00' || strict_date_optional_time || epoch_millis"
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2、实体类中同样声明该时间格式

@Data
@Document(indexName = "date_custom",shards = 1, replicas = 0) 
public class DateTest implements Serializable {

	@Field(type = FieldType.Date, name = "create_time",format = {},
		pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd'T'HH:mm:ss'+08:00' || strict_date_optional_time || epoch_millis")
	private Date createTime;

	// 旧版本
	// @Field(type = FieldType.Date, name = "create_time",format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd'T'HH:mm:ss'+08:00' || strict_date_optional_time || epoch_millis")
	// private Date createTime;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

es自带的时间格式,可以在官方文档中找到
在这里插入图片描述

2.2 配置日期格式转换器

1、ElasticsearchRestTemplate的构造方法中提供了一个构造方法,可以传入指定的EntityMapper

@Bean
	public ElasticsearchRestTemplate elasticsearchRestTemplate(RestHighLevelClient elasticsearchClient,EntityMapper entityMapper){
		return new ElasticsearchRestTemplate(elasticsearchClient,entityMapper);
	}
  • 1
  • 2
  • 3
  • 4

2、EntityMapper有两个实现类,其中ElasticsearchEntityMapper实现类提供了一个自定义转换器的方法setConversions

@Bean
	@Override
	public EntityMapper entityMapper() {
		ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
		entityMapper.setConversions(elasticsearchCustomConversions());
		return entityMapper;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3、通过该方法我们可以将我们自定义的转换器StringToDateConverter传入进去

@Override
	public ElasticsearchCustomConversions elasticsearchCustomConversions(){
		List<Converter<?,?>> converterList = Lists.newArrayList(StringToDateConverter.INSTANT);
		return new ElasticsearchCustomConversions(converterList);
	}
  • 1
  • 2
  • 3
  • 4
  • 5

4、完整代码

import com.google.common.collect.Lists;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
import org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.NonNull;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.datedemo")
public class ElasticRestClientConfig extends AbstractElasticsearchConfiguration {

	@Value("${spring.elasticsearch.rest.uris}")
	private String url;
	@Value("${spring.elasticsearch.rest.username}")
	private String username;
	@Value("${spring.elasticsearch.rest.password}")
	private String password;

	@Override
	@Bean
	public RestHighLevelClient elasticsearchClient() {
		url = url.replace("http://","");
		HttpHeaders headers = new HttpHeaders();
		headers.setBasicAuth(username,password);
		final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
			.connectedTo(url)
			.withDefaultHeaders(headers)
			.build();
		return RestClients.create(clientConfiguration).rest();
	}

	@Bean
	public ElasticsearchRestTemplate elasticsearchRestTemplate(RestHighLevelClient elasticsearchClient,EntityMapper entityMapper){
		return new ElasticsearchRestTemplate(elasticsearchClient,entityMapper);
	}

	/**
	 * 指定EntityMapper为ElasticsearchEntityMapper
	 * 解决es mapper映射实体类问题
	 * @return
	 */
	@Bean
	@Override
	public EntityMapper entityMapper() {
		ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
		entityMapper.setConversions(elasticsearchCustomConversions());
		return entityMapper;
	}

	/**
	 * 指定日期转换器,解决日期转换错误问题
	 * @return
	 */
	@Override
	public ElasticsearchCustomConversions elasticsearchCustomConversions(){
		List<Converter<?,?>> converterList = Lists.newArrayList(StringToDateConverter.INSTANT);
		return new ElasticsearchCustomConversions(converterList);
	}

	/**
	 * 字符串转换日期
	 */
	private enum StringToDateConverter implements Converter<String, java.util.Date> {
		/**
		 * 转换器实例
		 */
		INSTANT;

		@Override
		public Date convert(@NonNull String source) {
			try {
				return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+08:00'").parse(source);
			} catch (ParseException e) {
				e.printStackTrace();
			}
			return null;
		}
	}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

2.3 配置文件设置spring.jackson.date-format

配置文件中设置时间类型格式,但这种方式全局生效,小心使用

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

  • 1
  • 2
  • 3
  • 4

2.4 单独创建日期类型转换类

@Component
public class DateConverter implements Converter<String, Date> {

    @Override
    public Date convert(@NonNull String source) {
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(source);
        } catch (ParseException e) {
            try {
                return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+08:00'").parse(source);
            } catch (ParseException ex) {
                try {
                    return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(source);
                } catch (ParseException ex2) {
                    ex2.printStackTrace();
                }
            }
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.5 @DateTimeFormat注解声明格式(不生效)

首先说明一下这种方式并不生效,这里单独说明是为了列举出来,让大家避免走弯路。

该种方法的原理是通过在注解中声明自定义格式来实现格式转换,但实际上这并不起作用,因为spring-data-elasticsearch会忽略@DateTimeFormat注解

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") //返回时间类型
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") //接收时间类型
private Date startTime;
  • 1
  • 2
  • 3
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/837319
推荐阅读
相关标签
  

闽ICP备14008679号