Elasticsearch 的API 分为 REST Client API(http请求形式)以及 transportClient API两种。相比来说transportClient API效率更高,transportClient 是通过Elasticsearch内部RPC的形式进行请求的,连接可以是一个长连接,相当于是把客户端的请求当成Elasticsearch 集群的一个节点,当然 REST Client API 也支持http keepAlive形式的长连接,只是非内部RPC形式。但是从Elasticsearch 7 后就会移除transportClient 。主要原因是transportClient 难以向下兼容版本。
spring将elasticsearch过度封装,让开发者很难跟ES的DSL查询语句进行关联。再者就是更新速度,ES的更新速度是非常快的,但是spring-data-elasticsearch更新速度比较缓慢。
另外,spring-data-elasticsearch底层依赖的是TransportClient。而TransportClient在ES7中已被弃用,取而代之的是 Java 高级 REST 客户端,并将在 Elasticsearch 8.0 中删除。
es在7.15.0及以后版本放弃对旧版本中的Java REST Client (High Level Rest Client (HLRC))支持,推荐使用的Java API Client 8.x
<dependency>
<!--es的starter, 主要是为了实现自动化配置,方便快捷的获取rest client-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
说到底硕放底层还是对如下jar的封装
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
</dependency>
因此这里我就按照官方文档使用了推荐的,同时最小依赖原则
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.5.0</version>
</dependency>
<!-- 依赖的 jakarta 2.X 低版本 要 升级-->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.0.1</version>
</dependency>
bootstrap.yml增加自定义配置如下:
elasticsearch:
#ES的连接地址,多个地址用逗号分隔
uris: 1.13.102.59:9200
username:
password:
接下来 声明一个配置类ElasticSearchConfig,创建一个客户端
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.commons.compress.utils.Lists;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @author frank
* @version 1.0
*/
@Configuration
public class ElasticSearchConfig {
@Value("${elasticsearch.uris}")
private String uris;
@Value("${elasticsearch.username}")
private String name;
@Value("${elasticsearch.password}")
private String password;
@Bean
public ElasticsearchClient elasticsearchClient() {
ElasticsearchTransport transport = new RestClientTransport(restClient(), new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
@Bean
public RestClient restClient() {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(name, password));
List<HttpHost> httpHosts = Lists.newArrayList();
String[] split = uris.split(",");
for (int i = 0; i < split.length; i++) {
httpHosts.add(HttpHost.create(split[i]));
}
HttpHost[] httpHosts1 = httpHosts.toArray(new HttpHost[0]);
RestClient client = RestClient
.builder(httpHosts1)
.setHttpClientConfigCallback(httpAsyncClientBuilder ->
httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider).setKeepAliveStrategy((response, context) -> 180 * 1000))
.build();
return client;
}
}
接下来定义索引与文档的操作服务,忽略接口定义类,相关自定义类后面给出
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse;
import co.elastic.clients.elasticsearch.indices.GetIndexResponse;
import co.elastic.clients.elasticsearch.indices.IndexState;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author frank
* @version 1.0
*/
@Service
@Slf4j
public class EsearchServiceImpl{
@Autowired
private ElasticsearchClient client;
/**
* 判断索引是否存在
*
* @param indexName
* @return
* @throws IOException
*/
public boolean hasIndex(String indexName) throws IOException {
BooleanResponse exists = client.indices().exists(d -> d.index(indexName));
return exists.value();
}
/**
* 查询索引详情
*
* @param indexName
* @return
* @throws IOException
*/
public Map<String, IndexState> queryIndex(String indexName) {
try {
GetIndexResponse getIndexResponse = client.indices().get(i -> i.index(indexName));
//stringIndexStateMap.get(indexName).mappings().toString() //注意序列化问题
return getIndexResponse.result();
} catch (IOException e) {
e.printStackTrace();
throw new ServiceException("查询索引失败");
}
}
/**
* 删除索引
*
* @param indexName
* @throws IOException
*/
public boolean deleteIndex(String indexName) {
try {
DeleteIndexResponse response = client.indices().delete(d -> d.index(indexName));
return response.acknowledged();
} catch (IOException e) {
e.printStackTrace();
throw new ServiceException("删除索引失败");
}
}
/**
* 创建索引
*
* @param indexName
* @return
* @throws IOException
*/
public boolean createIndex(String indexName) {
try {
client.indices().create(c -> c.index(indexName));
} catch (IOException e) {
log.error("索引创建失败:{}", e.getMessage());
throw new ServiceException("创建索引失败");
}
return true;
}
/**
* 创建索引,不允许外部直接调用
*
* @param indexName
* @param mapping
* @throws IOException
*/
private boolean createIndex(String indexName, Map<String, Property> mapping) throws IOException {
CreateIndexResponse createIndexResponse = client.indices().create(c -> {
c.index(indexName).mappings(mappings -> mappings.properties(mapping));
return c;
});
return createIndexResponse.acknowledged();
}
/**
* 重新创建索引,如果已存在先删除
*
* @param indexName
* @param mapping
*/
public void reCreateIndex(String indexName, Map<String, Property> mapping) {
try {
if (this.hasIndex(indexName)) {
this.deleteIndex(indexName);
}
} catch (IOException e) {
e.printStackTrace();
throw new ServiceException("删除索引失败");
}
try {
this.createIndex(indexName, mapping);
} catch (IOException e) {
e.printStackTrace();
throw new ServiceException("重新创建索引失败");
}
}
/**
* 新增数据
*
* @param indexName
* @param id 自己的主键标识
* @throws IOException
*/
public String insertDocument(String indexName, Map<String, Object> obj, String id) {
try {
IndexRequest.Builder builder = new IndexRequest.Builder().index(indexName).document(obj);
if (StringUtils.isNotBlank(id)) {
builder.id(id);
}
IndexResponse index = client.index(builder.build());
return index.id();
} catch (IOException e) {
log.error("数据插入ES异常:{}", e.getMessage());
throw new ServiceException("ES新增数据失败");
}
}
/**
* 修改数据
*
* @param indexName
* @param id 自己的主键标识
* @throws IOException
*/
public boolean updateDocument(String indexName, Map<String, Object> obj, String id) {
try {
client.update(u -> u
.index(indexName)
.id(id)
.doc(obj)
, String.class);
return true;
} catch (IOException e) {
log.error("数据修改ES异常:{}", e.getMessage());
throw new ServiceException("ES修改数据失败");
}
}
/**
* 查询数据
*
* @param indexName
* @param id
* @return
*/
public Map searchDocument(String indexName, String id) {
try {
GetResponse<Map> getResponse = client.get(g -> g
.index(indexName)
.id(id)
, Map.class
);
return getResponse.source();
} catch (IOException e) {
log.error("查询ES异常:{}", e.getMessage());
throw new ServiceException("查询ES数据失败");
}
}
/**
* 删除数据
*
* @param indexName
* @param id
* @return
*/
public boolean deleteDocument(String indexName, String id) {
try {
client.delete(d -> d
.index(indexName)
.id(id)
);
} catch (IOException e) {
log.error("删除Es数据异常:{}", e.getMessage());
throw new ServiceException("数据删除失败");
}
return true;
}
/**
* 批量增加数据
*
* @param indexName
* @return
*/
public boolean batchInsertDocument(String indexName, List<Map<String, Object>> objs) {
try {
List<BulkOperation> bulkOperationArrayList = new ArrayList<>();
for (Map<String, Object> obj : objs) {
bulkOperationArrayList.add(BulkOperation.of(o -> o.index(i -> i.document(obj))));
}
client.bulk(b -> b.index(indexName).operations(bulkOperationArrayList));
return true;
} catch (IOException e) {
log.error("数据插入ES异常:{}", e.getMessage());
throw new ServiceException("ES新增数据失败");
}
}
/**
* 非主键查询
*
* @param indexName
* @return
*/
private TableDataInfo searchDocumentList(String indexName, Query query, EsPage esPage) {
try {
SearchRequest.Builder request = new SearchRequest.Builder().index(indexName);
if (query != null) {
request.query(query);
}
request.from(esPage.getPageNum() - 1);
request.size(esPage.getPageSize());
if (esPage.getOrderBy() != null) {
request.sort(f -> f.field(o -> o.field(esPage.getOrderBy()).order("asc".equals(esPage.getType()) ? SortOrder.Asc : SortOrder.Desc)));
}
List<String> includes = esPage.getIncludes();
if (includes != null && includes.size() > 0) {
request.source(a -> a.filter(v -> v.includes(includes)));
}
SearchResponse<Map> search = client.search(request.build(), Map.class);
List<Map<String, Object>> data = new ArrayList<>();
HitsMetadata<Map> hits = search.hits();
TotalHits total = hits.total();
for (Hit<Map> hit : hits.hits()) {
data.add(hit.source());
}
return new TableDataInfo(data, (int) total.value());
} catch (IOException e) {
e.printStackTrace();
throw new ServiceException(String.format("【查询 -> 失败】从es中查询分析后的日志出错,错误信息为:{}", e.getMessage()));
}
}
@Override
public TableDataInfo searchTermDocumentList(String indexName, EsPage esPage) {
Query query = null;
String cond = esPage.getCond();
if (cond != null) {
List<Query> list = new ArrayList<>();
String[] split = cond.split(",");
for (int i = 0; i < split.length; i++) {
String[] split1 = split[i].split(":");
list.add(QueryBuilders.match()
.field(split1[0]).query(split1[1])
//.value(split1[1])
.build()._toQuery());
}
query = QueryBuilders.bool().must(list).build()._toQuery();
}
return searchDocumentList(indexName, query, esPage);
}
}
构建列表查询分页类
import java.util.List;
/**
* @author frank
* @version 1.0
*/
public class EsPage {
/**
* 当前记录起始索引
*/
private Integer pageNum = 1;
/**
* 查询条件 eg: name:doc,age:12
*/
private String cond;
/**
* 每页显示记录数
*/
private Integer pageSize = 10;
/**
* 排序列
*/
private String orderBy;
/**
* 排序的方向desc或者asc
*/
private String type = "asc";
//选择展示的字段,默认所有
private List<String> includes;
public List<String> getIncludes() {
return includes;
}
public void setIncludes(List<String> includes) {
this.includes = includes;
}
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public String getOrderBy() {
return orderBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
public String getCond() {
return cond;
}
public void setCond(String cond) {
this.cond = cond;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
表格分页数据对象 TableDataInfo
import java.io.Serializable;
import java.util.List;
public class TableDataInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private long total;
/**
* 列表数据
*/
private List<?> records;
/**
* 表格数据对象
*/
public TableDataInfo() {
}
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<?> list, int total) {
this.records = list;
this.total = total;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public List<?> getRecords() {
return records;
}
public void setRecords(List<?> records) {
this.records = records;
}
}