Spring Boot集成ElasticSearch


本文将详细介绍如何在 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 连接客户端还引入 JacksonObjectMapper 用于 Json 数据转化。

@Autowired
private ObjectMapper objectMapper;

@Autowired
public RestHighLevelClient restHighLevelClient;

2. 主键查询

根据文档的 ID 进行数据查询,注意这个 IDES 为每一条的文档单独的值,在新增数据若未指定将会自动填充。

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;
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录