0. 引言
我们在使用spring-data-elasticsearch,可能会出现查询结果为null,但返回的数据size是大于0的。或者某一部分字段有值,某一部分字段为null
其结果如下图所示,那么这个问题是怎么产生的呢?今天我们就来详细解析
1. 问题分析
因为我们使用的是spring-data-elasticsearch,在实体类中已经用@Field注解声明了实体类与es索引mapping之间的映射关系,但是从查询结果来看,数据是已经查询出来了,不然size也不会大于0.
那么问题就在于实体类与es索引之间的映射关系没有生效。于是我们下一步就是去探索为什么没有生效的原因。
2.问题解决
2.1 实体类与es mapping中的属性名保持一致
仔细观察数据我们会发现,并不是所有的字段都没有对应上,实体类和索引同名的字段值都是正常显示的,但不同名的就没有正常显示。
那么最简单的解决办法就是将实体类中的属性名与es索引中的属性名保持一致,这样就能直接对应上了
但这个方法我不太推荐,因为有的时候mapping可能已经创建好了并且已经在之前的代码中使用了,不可能再去调整之前的代码。于是我们引入了第二种解决方案
2.2 使用@JsonProperty给实体类属性添加别名
既然是实体类名称与es mapping中的名称不一致,那么我们可以通过添加@JsonProperty注解来给实体类中的属性设置别名,其别名与es mapping保持一致即可
比如
@JsonProperty("project_id”)
private Long projectId;
但这个方法会导致我们输出给前端的属性名称也是@JsonProperty中声明的名称,有的时候我们就是想要使用实体类的名称,而且每个字段中都添加上这么一个注解,也实在太累。不是“懒人”的风格。说到底,并没有解决我们的根本问题:明明已经在实体类中@Field注解上声明了es mapping中的字段名,为什么不能利用@Field注解来自动转换呢?
2.3 使用ElasticsearchEntityMapper替换默认的EntityMapper
在实现第三种方案前,我们先把问题的原因分析清楚,先找到spring-data-elasticsearch将查询到的es结果与实体类对应的代码位置
我们从源头开始追溯,我们是通过调用queryForPage方法来实现查询的,那么找到该方法的源码
从方法名称可以猜出其通过一个mapper,mapResults方法来实现索引与实体类的映射,我们点击进mapResults方法看看
发现这是一个接口类,该接口下有两个实现类,我们点击进默认的DefaultResultMapper去看看
会发现该类的mapResults方法下使用了一个mapEntity方法来进行索引与实体类的映射
(不常阅读源码的同学可能会有疑惑,你怎么知道这个方法是用来做索引和实体类的映射的?其实先是凭借经验去猜,我们通过查看方法名也能猜的八九不离十了,mapEntity,译为映射实体类。,还要更直白些吗?这也提示我们,写书代码时方法名一定要能反映业务逻辑)
进入该方法,发现这是个接口类,并且调用了getEntityMapper()方法的mapToObject方法。不用说这个mapToObject方法一定是映射方法了
该方法是一个EntityMapper接口提供,该接口下提供了两个实现类
这个时候,有经验的同学可能已经嗅到一丝气息了。从这个命名来看,DefaultEntityMapper明显是默认的EntityMapper,也就是说默认是使用该类提供的映射方法。而ElasticsearchEntityMapper才是es相关的映射方法
这个时候其实问题已经解决大部分了,如果你不想继续阅读源码的话,可以直接搜索该类的作用已经两个实现类之间的区别。很快就能得出答案。
之后的代码比较晦涩且链路较长,我就不带大家详细解读了,给出调用过程,有兴趣的同学可以自己去翻翻
从该调用过程可以知道,ElasticsearchEntityMapper中使用识别@Field注解的,而DefaultEntityMapper会忽略该注解,因此我们需要做的就是在项目中显示声明使用ElasticsearchEntityMapper
配置方法也不难,就是创建一个配置类,在elasticsearchRestTemplate这个Bean的声明方法中将ElasticsearchEntityMapper作为参数传入。后续使用elasticsearchRestTemplate时就会使用ElasticsearchEntityMapper作为映射方法提供类
同时声明注解@EnableElasticsearchRepositories(basePackages = "com.example.esdemo"),表明spring-data-elasticsearch方式声明的Repositories类也将使用ElasticsearchEntityMapper
package org.springblade.statistic.conf;
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.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.repository.config.EnableElasticsearchRepositories;
import org.springframework.http.HttpHeaders;
/**
* @author whx
* @date 2022/5/23
*/
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.esdemo")
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;
}
}
如果你觉得本文对你的学习有帮助的话,不妨点赞支持一下吧