Elasticsearch Java API Client

By | 2022년 7월 1일
Table of Content

Elasticsearch Java API Client

참조

참조

대강 보았는데, API 가 꽤나 깔끔하다.

의존성 추가

def elasticSearchJavaApiClientVersion = '8.3.1'

dependencies {
    implementation "co.elastic.clients:elasticsearch-java:${elasticSearchJavaApiClientVersion}"
    implementation "com.fasterxml.jackson.core:jackson-databind:2.13.3"
    implementation "com.fasterxml.jackson.core:jackson-core:2.13.3"
    implementation "org.glassfish:jakarta.json:2.0.1"
    // implementation "jakarta.json:jakarta.json-api:2.1.0"
}

컴포넌트 생성

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "elasticsearch")
@RequiredArgsConstructor
public class ElasticProperty {

    private List<ElasticProperty.Elasticsearch> server;

    @Getter
    public static class Elasticsearch {

        private String ip;
        private int port;
        private String protocol;

        public HttpHost getHttpHost(){
            return new HttpHost(this.ip,this.port,this.protocol);
        }
    }
}
@Slf4j
@Component
@RequiredArgsConstructor
public class ElasticsearchClientConfig {

    private final ElasticProperty elasticProperty;

    @Bean
    ElasticsearchClient getJavaApiClient() {

        HttpHost[] httpHost = new HttpHost[elasticProperty.getServer().size()];
        int idx = 0;
        for(ElasticProperty.Elasticsearch elasticsearch : elasticProperty.getServer()){
            httpHost[idx++] = elasticsearch.getHttpHost();
        }

        RestClient restClient = RestClient.builder(httpHost).build();

        ObjectMapper mapper = new ObjectMapper()
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .registerModule(new JavaTimeModule());

        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper(mapper));

        return new ElasticsearchClient(transport);
    }
}

repository

@Slf4j
@Component
@RequiredArgsConstructor
public class ElasticsearchRepository {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private final ElasticsearchClient client;

    public boolean createIndex(CreateIndexProperty property) throws IOException {

        CreateIndexRequest request = CreateIndexRequest.of(builder ->
                builder.index(property.getName()).withJson(property.getSettings())
        );
        CreateIndexResponse response = client.indices().create(request);

        return Boolean.TRUE.equals(response.acknowledged());
    }

    public boolean deleteIndex(DeleteIndexProperty property) throws IOException {
        DeleteIndexRequest request = DeleteIndexRequest.of(builder ->
                builder.index(property.getName())
        );
        DeleteIndexResponse response = client.indices().delete(request);

        return Boolean.TRUE.equals(response.acknowledged());
    }

    public boolean indexExists(ExistsIndexProperty property) throws IOException {
        ExistsRequest request = ExistsRequest.of(builder ->
                builder.index(property.getName())
        );
        BooleanResponse response = client.indices().exists(request);

        return response.value();
    }

    public long createOrUpdateDocument(ItemDocument itemDocument) throws IOException {
        IndexRequest<ItemDocument> request = IndexRequest.of(builder -> builder
                .index(IndexName.ITEM)
                .id(itemDocument.getId().toString())
                .document(itemDocument)
        );

        IndexResponse response = client.index(request);

        return response.version();
    }

    public ItemDocument getDocument() throws IOException {
        GetResponse<ItemDocument> response = client.get(builder -> builder
                        .index(IndexName.ITEM)
                        .id("1"),
                ItemDocument.class
        );

        if (response.found()) {
            return response.source();
        } else {
            logger.info ("Document not found");
        }

        return null;
    }

    public List<Hit<ItemDocument>> search() throws IOException {
        String searchText = "test";

        // 중요 : 검색은 create/update 후 일정시간이 지나야 데이타를 조회할 수 있다.
        SearchResponse<ItemDocument> response = client.search(s -> s
                        .index(IndexName.ITEM)
                        .query(q -> q
                                .match(t -> t
                                        .field("mimetype")
                                        .query(searchText)
                                )
                        ),
                ItemDocument.class
        );

        TotalHits total = response.hits().total();
        assert total != null;
        boolean isExactResult = total.relation() == TotalHitsRelation.Eq;

        if (isExactResult) {
            logger.info("There are " + total.value() + " results");
            Query query = SearchRequest.of(builder -> builder
                    .index(IndexName.ITEM)
                    .query(q -> q
                            .match(t -> t
                                    .field("mimetype")
                                    .query(searchText)
                            )
                    )).query();
            logger.info("query : {}", query);

            logger.info("response : {}", response);
            logger.info("response.hits() : {}", response.hits());
        } else {
            logger.info("There are more than " + total.value() + " results");
        }

        return response.hits().hits();
    }
}

service

create_item_index.json

{
  "settings": {
    "number_of_shards": 5,
    "max_ngram_diff": 2,
    "analysis": {
      "tokenizer": {
        "ngram_tokenizer": {
          "type": "ngram",
          "min_gram": "1",
          "max_gram": "3",
          "token_chars": ["letter", "digit", "punctuation", "symbol"]
        },
        "unix_path_tokenizer": {
          "type": "path_hierarchy",
          "buffer_size": 1024,
          "replacement": "/",
          "reverse": "false",
          "skip": 0,
          "delimiter": "/"
        },
        "whitespace_tokenizer": {
          "type": "whitespace"
        },
        "keyword_tokenizer": {
          "type": "keyword",
          "buffer_size": 1024
        }
      },
      "analyzer": {
        "ngram_analyzer": {
          "type": "custom",
          "filter": [
            "lowercase"
          ],
          "tokenizer": "whitespace"
        },
        "lowercase_analyzer": {
          "type": "custom",
          "tokenizer": "keyword",
          "filter": ["lowercase"]
        },
        "directory_path_analyzer": {
          "type": "custom",
          "tokenizer": "unix_path_tokenizer"
        },
        "whitespace_analyzer": {
          "type": "custom",
          "tokenizer": "whitespace_tokenizer"
        },
        "keyword_analyzer": {
          "type": "custom",
          "tokenizer": "keyword_tokenizer"
        }
      },
      "normalizer": {
        "lowercase_normalizer": {
          "type": "custom",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "ngram_analyzer",
        "fields": {
          "lowercase": {
            "type": "keyword",
            "normalizer": "lowercase_normalizer"
          }
        }
      },
      "path": {
        "type": "text",
        "analyzer": "directory_path_analyzer",
        "fields": {
          "full": {
            "type": "keyword"
          }
        }
      },
      "originalSize": {
        "type": "double",
        "store": "true"
      },
      "assetCategory": {
        "type": "text",
        "analyzer": "keyword_analyzer",
        "search_analyzer": "whitespace_analyzer",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "mimetype": {
        "type": "keyword"
      },
      "importedBy": {
        "type": "integer",
        "store": "true"
      },
      "updatedBy": {
        "type": "keyword"
      },
      "importedAt": {
        "type": "date",
        "store": "true"
      },
      "updatedAt": {
        "type": "date",
        "store": "true"
      },
      "fileCreatedAt": {
        "type": "date",
        "store": "true"
      },
      "fileUpdatedAt": {
        "type": "date",
        "store": "true"
      },
      "metadataSet": {
        "type": "long"
      },
      "instanceId": {
        "type": "keyword"
      },
      "referenceId": {
        "type": "keyword"
      },
      "cutComment": {
        "type": "text",
        "analyzer": "ngram_analyzer",
        "fields": {
          "lowercase": {
            "type": "text",
            "analyzer": "lowercase_analyzer"
          }
        }
      },
      "comment": {
        "properties": {
          "userId": {
            "type": "long"
          },
          "value": {
            "type": "text",
            "analyzer": "ngram_analyzer",
            "fields": {
              "lowercase": {
                "type": "text",
                "analyzer": "lowercase_analyzer"
              }
            }
          },
          "updatedAt": {
            "type": "date"
          }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "ngram_analyzer"
      },
      "shadow": {
        "type": "boolean",
        "store": "true"
      },
      "shadowUpdatedAt": {
        "type": "date",
        "store": "true"
      },
      "downloadValue": {
        "type": "long"
      },
      "collection": {
        "type": "long"
      },
      "sha1": {
        "type": "keyword"
      },
      "subtitle": {
        "type": "text",
        "analyzer": "ngram_analyzer"
      },
      "videoOcr": {
        "type": "text",
        "analyzer": "ngram_analyzer"
      },
      "version": {
        "type": "long"
      }
    },
    "dynamic_templates": [
      {
        "cmeta_str": {
          "match": "cmeta_str-*",
          "mapping": {
            "type": "text",
            "store": "true",
            "analyzer": "ngram_analyzer",
            "fields": {
              "lowercase": {
                "type": "keyword",
                "normalizer": "lowercase_normalizer"
              }
            }
          }
        }
      },
      {
        "cmeta_select": {
          "match": "cmeta_select-*",
          "mapping": {
            "type": "text",
            "store": "true",
            "analyzer": "ngram_analyzer",
            "fields": {
              "lowercase": {
                "type": "keyword",
                "normalizer": "lowercase_normalizer"
              }
            }
          }
        }
      },
      {
        "cmeta_bool": {
          "match": "cmeta_bool-*",
          "mapping": {
            "type": "boolean",
            "store": "true"
          }
        }
      },
      {
        "cmeta_double": {
          "match": "cmeta_double-*",
          "mapping": {
            "type": "double",
            "store": "true"
          }
        }
      },
      {
        "cmeta_date": {
          "match": "cmeta_date-*",
          "mapping": {
            "type": "date",
            "store": "true"
          }
        }
      },
      {
        "cmeta_multi_label": {
          "match": "cmeta_multi_label-*",
          "mapping": {
            "type": "long",
            "store": "true"
          }
        }
      }
    ]
  }
}
@Service
@RequiredArgsConstructor
public class ItemIndexService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private final ElasticsearchRepository repository;

    public boolean createIndex() throws IOException {

        CreateIndexProperty property = new CreateIndexProperty();
        ClassPathResource resource = new ClassPathResource("json/create_item_index.json");
        InputStream input = resource.getInputStream();
        property.setName(IndexName.ITEM);
        property.setSettings(input);

        ExistsIndexProperty existsIndexProperty = new ExistsIndexProperty();
        existsIndexProperty.setName(IndexName.ITEM);

        if (repository.indexExists(existsIndexProperty)) {
            logger.debug("index exists");
            DeleteIndexProperty deleteIndexProperty = new DeleteIndexProperty();
            deleteIndexProperty.setName(IndexName.ITEM);
            repository.deleteIndex(deleteIndexProperty);
            logger.debug("index deleted");
        }

        return repository.createIndex(property);
    }

    public void addDocument() throws IOException {
        ItemDocument itemDocument = new ItemDocument();
        itemDocument.setId(1);
        itemDocument.setMimetype("test");

        long version = repository.createOrUpdateDocument(itemDocument);
        logger.debug("Document version : {}", version);
    }

    public void getDocument() throws IOException {
        ItemDocument itemDocument = repository.getDocument();

        if (itemDocument != null) {
            logger.info("Mimetype :  " + itemDocument.getMimetype());
        }
    }

    public void search() throws IOException {
        List<Hit<ItemDocument>> hits = repository.search();
        for (Hit<ItemDocument> hit: hits) {
            ItemDocument product = hit.source();
            logger.info("Found Item " + product.getMimetype() + ", score " + hit.score());
        }
    }
}

controller

@Slf4j
@RequestMapping("apis/index")
@RestController
@RequiredArgsConstructor
public class IndexController {

    private final ItemIndexService itemIndexService;

    @PostMapping("/create/item")
    @Operation(summary = "Item Index 생성", description ="Item Index 를 생성 한다")
    @ApiResponses({
            @ApiResponse(code = 0, message = "요청 성공"),
            @ApiResponse(code = 60000, message = "색인 생성에 실패하였습니다.")
    })
    public ResultResponse<EmptyResultModel> createItemIndex() throws Exception {
        // itemIndexService.createIndex();
        itemIndexService.addDocument();
        itemIndexService.getDocument();
        itemIndexService.search();
        return new ResultResponse<>(new EmptyResultModel(StringUtils.EMPTY));

    }
}

답글 남기기