本文将详细介绍如何在
Spring Boot
项目中集成ElasticSearch
。
一、基础配置
1. 依赖导入
在新建的项目并引入相关依赖,因为 ES
数据为 Json
格式所以这里引用了 Jackson
用于数据转换。
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.1.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.1.0</version>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
2. 项目配置
在项目配置文件 application.yml
中添加如下配置:
elasticsearch:
# 连接信息
host: 10.231.6.65
port: 9200
username: elastic
password: 123456
二、客户端连接
1. Bean配置
新建 ESConfig
配置类用于注入 ES bean
客户端连接实例对象。
@Configuration
public class ESConfig {
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private int port;
@Value("${elasticsearch.username}")
private String userName;
@Value("${elasticsearch.password}")
private String password;
/**
* 注入 RestHighLevelClient 依赖到 spring 容器中待用
*/
@Bean(name = "restHighLevelClient", destroyMethod = "close")
public RestHighLevelClient initRestClient() {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
// 用户信息
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(userName, password));
// 构建连接对象
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port))
// 配置连接超时
.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder.setConnectTimeout(60 * 1000)
.setSocketTimeout(5 * 60 * 1000)
.setConnectionRequestTimeout(60 * 1000))
// 异步连接数配置
.setHttpClientConfigCallback(httpClientBuilder -> {
httpClientBuilder.disableAuthCaching();
httpClientBuilder.setMaxConnTotal(50);
httpClientBuilder.setMaxConnPerRoute(50);
// 用户认证
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
});
return new RestHighLevelClient(builder);
}
}
2. 连接实例
在完成连接客户端配置之后我们即可通过 @Autowired
获取上面注入的 bean
对象。
通过 ping()
方法测试当前客户端是否连接有效。
public class MyTest {
@Autowired
private RestHighLevelClient restHighLevelClient;
public void testConnect() throws IOException {
boolean ping = restHighLevelClient.ping(RequestOptions.DEFAULT);
System.out.println(ping);
}
}
三、数据索引
1. 索引创建
根据 mapping
内容创建相应的索引,若未指定将会自动根据数据内容匹配。
public String mapping() {
return "{\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"info\": {\n" +
" \"type\": \"text\"\n" +
" },\n" +
" \"createDate\": {\n" +
" \"type\": \"keyword\"\n" +
" }\n" +
" }" +
"}";
}
public boolean createIndex(String indexName) {
CreateIndexRequest request = new CreateIndexRequest(indexName);
// 配置 mapping
request.mapping(mapping(), XContentType.JSON);
// 配置索引分片、副本
Settings.Builder builder = Settings.builder()
.put("index.number_of_shards", 3)
.put("index.number_of_replicas", 5)
.put("index.max_result_window", 2147483647);
request.settings(builder);
CreateIndexResponse response;
try {
response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
} catch (IOException e) {
logger.error("索引创建异常" + e);
throw new RuntimeException(e);
}
return response.isAcknowledged();
}
2. 存在判断
构建请求判断当前 ES
中是否已存在该索引。
public boolean isExist(String indexName) {
boolean isExists;
GetIndexRequest request = new GetIndexRequest(indexName);
try {
isExists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
} catch (IOException e) {
logger.error("判断索引存在异常" + e);
throw new RuntimeException(e);
}
return isExists;
}
3. 索引遍历
通过构建请求获取当前 ES
下所有索引名称。
public void listIndex() {
try {
GetAliasesRequest request = new GetAliasesRequest();
GetAliasesResponse getAliasesResponse = restHighLevelClient.indices().getAlias(request, RequestOptions.DEFAULT);
Map<String, Set<AliasMetaData>> map = getAliasesResponse.getAliases();
Set<String> indices = map.keySet();
for (String key : indices) {
System.out.println(key);
}
} catch (IOException e) {
e.printStackTrace();
}
}
4. 索引删除
根据名称删除指定索引,其对应数据将一同删除。
public boolean deleteIndex(String indexName) {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
AcknowledgedResponse response;
try {
response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
} catch (Exception e) {
logger.error("索引 {} 删除异常", indexName);
throw new RuntimeException(e);
}
return response.isAcknowledged();
}
四、数据操作
1. 基础配置
这里除了需要引入 ES
连接客户端还引入 Jackson
的 ObjectMapper
用于 Json
数据转化。
@Autowired
private ObjectMapper objectMapper;
@Autowired
public RestHighLevelClient restHighLevelClient;
2. 主键查询
根据文档的 ID
进行数据查询,注意这个 ID
是 ES
为每一条的文档单独的值,在新增数据若未指定将会自动填充。
public User get(String index, String id) {
GetRequest request = new GetRequest();
request.index(index);
request.id(id);
User user;
GetResponse response;
try {
response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
// Json 数据转化
user = objectMapper.readValue(response.getSourceAsString(), User.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
return user;
}
3. 数据新增
在指定索引下添加数据。
public String save(String index, String id, User user) {
IndexRequest request = new IndexRequest();
IndexResponse response;
request.index(index);
request.id(id);
try {
request.source(objectMapper.writeValueAsString(user), XContentType.JSON);
response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException(e);
}
return response.getResult().getLowercase();
}
4. 数据更新
根据索引名与文档 ID
更新数据。
public String update(String index, String id, User user) {
UpdateRequest request = new UpdateRequest();
request.index(index);
request.id(id);
UpdateResponse response;
try {
request.doc(objectMapper.writeValueAsString(user), XContentType.JSON);
response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException(e);
}
return response.getResult().getLowercase();
}
5. 数据删除
根据索引名与文档 ID
删除指定数据。
public String deleted(String index, String id) {
DeleteRequest request = new DeleteRequest();
request.index(index);
request.id(id);
DeleteResponse response;
try {
response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException(e);
}
return response.getResult().getLowercase();
}
五、数据插叙
1. 分页查询
通过 SearchSourceBuilder
构造查询条件,设置为 QueryBuilders.matchAllQuery()
则不执行任何过滤。
默认查询返回的数据仅 10
条,因此需要通过设置 builder.trackTotalHits(true)
取消该限制。
public List<User> list(String index, Integer size, Integer from) {
SearchRequest request = new SearchRequest(index);
// 构造查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
// 查询所有数据
builder.query(QueryBuilders.matchAllQuery());
// 取消条数限制
builder.trackTotalHits(true);
// 分页条件
builder.size(size);
builder.from(from);
// 设置请求查询条件
request.source(builder);
List<User> list;
try {
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
list = Arrays.stream(response.getHits().getHits()).map(p -> {
try {
return objectMapper.readValue(p.getSourceAsString(), User.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException(e);
}
return list;
}
2. 精准查询
ES
中通过 BoolQueryBuilder
构建查询条件,类似 SQL
语句中的 where
条件。
BoolQueryBuilder
具体的条件参数参考下表:
方法 | 作用 |
---|---|
termQuery() | 精准查询,等价于 where id = 1。 |
termQuerys() | 区间查询,等价于 where id in (1)。 |
查询的具体步骤与分页查询操作类型,只需要将分页查询中 builder.query(QueryBuilders.matchAllQuery())
条件替换为对应下述构造得到的结果即可。
因此此处仅介绍如何构造精准查询的 QueryBuilders
查询条件,示例代码如下:
public class Condition {
private QueryType queryType;
private String fieldName;
private Object fieldValues;
}
public class QueryTest {
private BoolQueryBuilder createQueryBuilder(List<Condition> conditionList) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
for (Condition condition : conditionList) {
QueryType queryType = condition.getQueryType();
String fieldName = condition.getFieldName();
Object fieldValues = condition.getFieldValues();
QueryBuilder queryBuilder;
switch (queryType) {
case EQUAL:
// termQuery(): "where fieldName = fieldValues"
queryBuilder = QueryBuilders.termQuery(fieldName, fieldValues);
break;
case NOT_EQUAL:
queryBuilder = QueryBuilders.boolQuery()
.mustNot(QueryBuilders.matchQuery(fieldName, fieldValues));
break;
case IN:
// termsQuery(): "where fieldName in (fieldValues...)"
queryBuilder = QueryBuilders.termsQuery(fieldName, fieldValues);
break;
case NOT_IN:
queryBuilder = QueryBuilders.boolQuery()
.mustNot(QueryBuilders.termsQuery(fieldName, fieldValues));
break;
default:
continue;
}
// "filter()" more efficient then "must()"
boolQueryBuilder.filter(queryBuilder);
}
return boolQueryBuilder;
}
}
3. 模糊查询
通过 fuzzyQuery()
可实现模糊查询,类似 like
查询效果,并可以利用 fuzziness()
进一步控制查询条件。
如通过 Fuzziness.ONE
用于控制查询为目标值获取长度比目标值多 1 的数据,如查询模糊 张
则 张三
、 张四
都是满足条件的 但是 张三1
因为比 张
多两个字符则不满条件,当然也可以设置 Fuzziness.TWO
则超过两个字符的也满足条件。
public List<User> vagueQuery(String indexName, String name) {
SearchRequest request = new SearchRequest(indexName);
SearchSourceBuilder builder = new SearchSourceBuilder();
// 查询名称中包含 "张三" 的数据,或者比 "张三" 多一个字符的数据,
builder.query(QueryBuilders.fuzzyQuery("name", name).fuzziness(Fuzziness.ONE));
request.source(builder);
List<User> userList = new ArrayList<>();
try {
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
for (SearchHit hit : response.getHits().getHits()) {
userList.add(objectMapper.readValue(hit.getSourceAsString(), User.class));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return userList;
}
4. 范围查询
通过 rangeQuery()
实现区间查询,类似 SQL
语句中的 >, >=, <, <=
等关键字效果。
常见比较符号与 rangeQuery()
方法参数对应参考下表:
范围符 | ES 关键字 |
---|---|
> | gt() |
>= | gte() |
< | lt() |
<= | lte() |
如下示例中查询年龄字段值 min <= age <= max
的数据。
public List<User> filterQuery(String indexName, int min, int max) {
SearchRequest request = new SearchRequest()indexName;
// 构造查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 查询年龄大于等于 min,小于等于 max 的结果
boolQueryBuilder.filter(QueryBuilders.rangeQuery("age").gte(min).lte(max));
builder.query(boolQueryBuilder);
request.source(builder);
List<User> userList = new ArrayList<>();
try {
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
for (SearchHit hit : response.getHits().getHits()) {
userList.add(objectMapper.readValue(hit.getSourceAsString(), User.class));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return userList;
}