项目场景:
在springboot项目中,针对elasticsearch读写遇到的问题记录.
问题描述及解决方案
1.WRITE
1.springboot不同版本与其对应的es版本也不同,使用的部分API有所变化,详情见下方
1.更新es数据所使用的UpdateQuery构建方式不同
2.elasticsearchRestTemplate()传参不同
springboot-2.3.2.RELEASE版本(使用的版本)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/>
</parent>
构建方式如下:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.BulkFailureException;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import java.lang.reflect.ParameterizedType;
import java.util.Set;
@Slf4j
public class EsService<T> {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 插入/更新
* @param idName
* @param object 数据
*/
public void upsert(String idName, T object) {
// 获取T.class
Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
IndexCoordinates indexCoordinates = elasticsearchRestTemplate.getIndexCoordinatesFor(entityClass);
try {
if (object == null) {
return;
}
JSONObject jsonObject = (JSONObject) JSON.toJSON(object);
UpdateQuery updateQuery = UpdateQuery.builder(jsonObject.getString(idName)).withDocument(Document.parse(jsonObject.toString())).withDocAsUpsert(true).build();
elasticsearchRestTemplate.update(updateQuery,indexCoordinates);
} catch (BulkFailureException e) {
//捕获更新/插入失败的异常,从而把失败数据的id取到
if (e.getFailedDocuments() != null){
Set<String> failedIds = e.getFailedDocuments().keySet();
for (String failedId : failedIds) {
log.warn("更新/插入失败的数据id:" + failedId);
}
}
}
}
}
springboot-2.2.2.RELEASE版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
构建方式如下:
@Slf4j
public class EsService<T> {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 插入/更新
* @param idName
* @param object 数据
*/
public void upsert(String idName, T object) {
// 获取T.class
Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
try {
if (object == null) {
return;
}
JSONObject jsonObject = (JSONObject) JSON.toJSON(object);
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.retryOnConflict(1);// 冲突重试
updateRequest.doc(jsonObject.toString(),XContentType.JSON);
UpdateQuery updateQuery = new UpdateQueryBuilder()
.withId(jsonObject.getString(idName))
.withUpdateRequest(updateRequest)
.withDoUpsert(true)
.withClass(entityClass)
.build();
elasticsearchRestTemplate.update(updateQuery);
} catch (BulkFailureException e) {
//捕获更新/插入失败的异常,从而把失败数据的id取到
if (e.getFailedDocuments() != null){
// 这里会报空指针异常,因为这个版本底层对异常进行包装了
Set<String> failedIds = e.getFailedDocuments().keySet();
for (String failedId : failedIds) {
log.warn("更新/插入失败的数据id:" + failedId);
}
}
}
}
}
注:如果在2.2.2版本中使用2.3.2版本API构建方式会报空指针异常
2.es不同版本导致索引类型‘type’不同
es旧版本支持自定义索引类型,比如type=”Company”
es新版本之后索引类型均为“_doc”
因此需要在实体类上通过Document注解进行指明,否则索引类型会自动生成,为实体类类名小写格式如:Company->company,不指明会因为数据类型问题报错
@Data
@Document(indexName = "company",type = "_doc", useServerConfiguration = true, createIndex = false)
@Mapping
public class Company {
@Field(type = FieldType.Object) // 这样才能使User里的@Field注解生效
private User user;
}
3.在实体中指明es索引字段数据类型使其生效的问题-@Field注解
场景描述:实体字段amount(举例)为BigDecimal类型,es本应该自动映射为double类型或float类型,但插入es的第一条数据amount值为整数导致es映射为long型,之后再插入带有小数点的数据也能插入成功,但进行sum统计时小数点后边的小数es会对其进行忽略,不进行计算。
问题解决:
之前也标注了@Field注解但并没有生效,查阅多方资料说是spring-data-elasticsearch根本没有使用该标签。按网上的解决方法是使用mappingPath(指明属性类型的配置文件路径)标签,不指明mappingPath才会根据@Field注解指明的类型
最终解决步骤如下:
(1).在实体类上标注@Mapping注解
(2)如果该实体包含其他实体,需要在包含的实体上标注@Field(type=FieldType.Object)
@Data
@Document(indexName = "company",type = "_doc", useServerConfiguration = true, createIndex = false)
@Mapping
public class Company {
@Field(type = FieldType.Object) // 这样才能使User里的@Field注解生效
private User user;
}
(3)在包含的实体中的字段上也标注上@Field注解
@Data
public class User {
@Field(type = FieldType.Double)
private BigDecimal amount;
}
(4)在插入数据之前执行putMapping操作
elasticsearchRestTemplate.putMapping(entityClass);
4.由于数据字段个数达到上限,默认为1000个,导致数据插入失败,报错如下:
问题解决:
在建索引时指定字段个数上限
PUT /索引名/_settings
{
"index.mapping.total_fields.limit": 2000
}
5.字段映射问题
在es建索引时不用指明数据具体字段名及数据类型,直接
put /索引名
建个索引就可以,es会自动映射出该索引的属性及数据类型,但出现了一些属性的映射有问题,比如某些字段为字符串类型,而第一条数据存的却是整数值,因此在es将该字段映射为long类型,导致再插入带有字母的数据时报错
问题解决:
将数据插入之前将该字段转化为String类型,这样在es会映射为keyword类型
6.批量插入es捕获到失败数据id报空指针异常
插入es失败的数据,ElasticsearchException会把失败数据的id写入到FailedDocument(Map)的key上(获取写入失败id见上方代码),由于springboot版本问题可能会对ElasticsearchException进行封装后再抛出,以至于直接取捕获到异常里的FailedDocument报空指针(封装后的异常可能没有该Map集合)
问题解决:
更换springboot版本:2.2.2更换为2.3.2
2.READ
1.判断条件tremsQuery
tremsQuery类似于sql in,所以传参可以是list集合
QueryBuilders.termsQuery(字段,集合),需要该字段的type为keyword
记得点赞收藏奥,后续开发遇到问题会实时更新,关注不迷路~