restTemplateでElasticsearchに問い合わせる

エキサイト株式会社 メディア事業部エンジニアの中尾です。

rest-high-level-clientのようなライブラリではなく、restTmeplateでElasticsearchに問い合わせる方法を説明します。

https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client

使う場面として、Elasticsearch5系のバージョン以下の場合、Elasticsearchで使っているオプションがライブラリで扱われていない(auto_generate_synonyms_phrase_query)などあるので、

DAOとして、以下のようにJsonのNodeを渡します。

@Component
@RequiredArgsConstructor
public class ElasticSearchDaoImpl implements ElasticsearchDao {

    @Value("${spring.elasticsearch.url}")
    public String url;

    private final RestTemplate restTemplate;

    private final ObjectMapper objectMapper;

    @Override
    public JsonNode searchRequest(JsonNode jsonNode) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>(jsonNode.toString(), headers);
        ResponseEntity<String> exchange =
                restTemplate.exchange(this.url, HttpMethod.POST, entity, String.class);

        if (exchange.getStatusCode() != HttpStatus.OK) {
            throw new ElasticsearchException(exchange.getBody());
        }
        try {
            return objectMapper.readTree(exchange.getBody());
        } catch (JsonProcessingException e) {
            throw new ElasticsearchException(e.getMessage());
        }
    }
}

どんなJsonNodeを渡すかというと、以下のようにElasticsearchに問い合わせるのに必要そうな値をセットするデータクラスを用意し、SearchSourceBuilder.toStringでstringにした後、objectMapperでJsonNode化します。

JsonNode jsonNode = objectMapper.readTree(elasticSearchForm.toString());
@Data
@Accessors(chain = true)
public class ElasticSearchForm {

    /**
     * Elasticsearchから取得するインデックス
     */
    private String index;

    /**
     * Elasticsearchから取得するカラム
     */
    private String[] includes;

    /**
     * Elasticsearchから取得しないカラム
     */
    private String[] excludes;

    /**
     * 検索条件
     */
    private BoolQueryBuilder boolQueryBuilder;

    /**
     * offset
     */
    private int from = 0;

    /**
     * limit
     */
    private int size = 10;

    /**
     * order
     */
    private FieldSortBuilder fieldSortBuilder;

    public String toString(){
        return new SearchSourceBuilder()
                .fetchSource(
                        includes,
                        excludes)
                .query(boolQueryBuilder)
                .from(from)
                .size(size)
                .sort(fieldSortBuilder)
                .toString();
    }
}

JsonNode化すれば、不要なところは以下のように消すことができます。

※本当はもっとネストしていると思いますが、簡略しています

((ObjectNode) jsonNode.get("query").........remove("auto_generate_synonyms_phrase_query");

あとは、このJsonNodeを先程のDAOの引数に入れるだけです。

レスポンスについて、以下のようにstreamを使えば、必要なデータのListが作れると思います。 他に必要なデータは適宜JsonNodeから取得してください。

                    StreamSupport.stream(
                            response.get("hits").get("hits").spliterator(), false)
                            ).map(e -> {
                                 // 処理
                                 }
                            )
                            .collect(Collectors.toList());

Elasticsearchに問い合わせるJsonはネストが深く、文字列結合やMapだとどうしても見通しが悪くなる場合があります。

Builderを使えるところは使って、それをjsonに変換した方が見通しがよくなると思うので、よかったら使ってください。