Table of Contents
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));
}
}