5. Spring + Elasticsearch 구현
이제 앞서 살펴본 Elasticsearch 문법들을 그대로 Spring에서 구현하는 것이 이 포스팅의 목표이다ㅓ
먼저 Spring에서 Elasticsearch 데이터를 관리&처리하기 위해 Spring Data Elasticsearch를 사용하였다
자세한 내용은 Spring Data Elasticsearch docs를 참고할 수 있다.
build.gradle
dependencies {
// elasticsearch
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
}
사실 Elasticsearch 포트 번호를 변경하거나, 인증을 적용한 게 아니라면 config를 만들어줄 필요가 없지만 허전하니 넣어두겠다
@EnableElasticsearchRepositories
@Configuration
public class ElasticsearchConfig extends ElasticsearchConfiguration {
@Value("${spring.data.elasticsearch.url}")
private String url;
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(url)
// 인증 추가 가능
.build();
}
// '_class' 필드 제거
@Override
protected boolean writeTypeHints() {
return false;
}
}
// application.yml
spring:
data:
elasticsearch:
repositories:
enabled: true
url: localhost:9200
4.3에서 생성했던 Analyzer, Tokenizer를 적용한 Post 인덱스를 Spring에서 생성해보자.
Elasticsearch Object Mapping과 MisCellaneous Elasticserach Operation Support를 참고했다
PostDocument
@Getter
@Document(indexName = "post", writeTypeHint = WriteTypeHint.FALSE) // '_class' 필드 제거
@Setting(settingPath = "elastic/post-document-setting.json") // post 인덱스에 대한 분석기, 토크나이저 설정
@Mapping(mappingPath = "elastic/post-document-mapping.json") // post가 저장&검색될 때 사용될 타입과 분석기 지정
public class PostDocument {
@Id
private String id;
private String title;
private String content;
public PostDocument(String title, String content) {
this.title = title;
this.content = content;
}
}
resources/post-document-setting.json
{
"analysis": {
"tokenizer": {
"nori_mixed": {
"type": "nori_tokenizer",
"decompound_mode": "mixed"
}
},
"analyzer": {
"nori_korean": {
"type": "custom",
"tokenizer": "nori_mixed"
}
}
}
}
resources/post-document-mapping.json
{
"properties": {
"title": {
"type": "text",
"analyzer": "nori_korean"
},
"content": {
"type": "text",
"analyzer": "nori_korean"
}
}
}
아까 만들어뒀던 Post 인덱스를 제거하고 프로젝트를 실행해보자
1. Elasticsearch - DELETE /post
2. 프로젝트 실행
3. Elasticsearch - GET /post/_settings, GET /post/_mapping
이제 match 쿼리로 데이터를 조회하기 위해 Elasticsearch Repository를 활용하였다
Elasticsearch Repository도 CRUD와 Paging을 상속받고 있어 JPA Repository와 매우 유사하게 사용할 수 있다
하지만 Elasticsearch Repository에서 제공하는 조회 기능 findX는 bool match 쿼리를 활용한다
따라서 match 쿼리로 데이터를 조회하려면 @Query를 통해 직접 구현해야 한다
이전 글에서 확인했듯이 match를 사용한 조회 쿼리는 query-match, query-match-query 구문을 활용할 수 있다
추가로 페이징을 적용하기 위해 반환타입에 Page, 파라미터에 Pageable만 추가해면 쿼리가 나갈 때 알맞는 데이터를 포함시킨다
{
"query": {
"match": {
"content": {
"query": "example"
}
}
},
"from": 10, // SQL의 offset
"size": 5 // 페이지 크기
}
PostDocumentRepository
@Repository
public interface PostDocumentRepository extends ElasticsearchRepository<PostDocument, String> {
// @Query("{\"match\": {\"content\": {\"query\": \"?0\"}}}")
@Query("{\"match\": {\"content\": \"?0\"}}")
List<PostDocument> findByContentContaining(String keyword);
// @Query("{\"match\": {\"content\": {\"query\": \"?0\"}}}")
@Query("{\"match\": {\"content\": \"?0\"}}")
Page<PostDocument> findByContentContaining(String keyword, Pageable pageable);
}
PostDocument의 필드는 분석기를 매핑해놨기 때문에 저장은 ElasticRepository의 save() 메소드를 그대로 활용하였다
PostDocumentController
단순 CRUD라 service 계층은 생략하였다
@PostMapping("/posts")
public ResponseEntity postDocument(@RequestParam String title,
@RequestParam String content) {
PostDocument postDocument = new PostDocument(title, content);
postDocumentRepository.save(postDocument);
return ResponseEntity.ok(postDocument.getId());
}
@GetMapping("/posts/_search")
public ResponseEntity search(@RequestParam String keyword,
@PageableDefault Pageable pageable) {
Page<PostDocument> postDocuments = postDocumentRepository.findByContentContaining(keyword, pageable);
return ResponseEntity.ok(postDocuments);
}
PostDocument 저장 및 검색 테스트
6. Elasticsearch의 전문 검색 인덱스 vs H2의 LIKE % 성능 비교
이어서
'Project > Elasticsearch의 전문 검색 인덱스 성능 비교' 카테고리의 다른 글
Spring + Elasticsearch 전문 검색 인덱스 적용 - Elasticsearch 익히기 (1) (0) | 2024.11.24 |
---|