[백업][가리사니] 스프링 데이터로 엘라스틱 서치 연동
elasticsearch, kotlin, spring
이 문서는 가리사니 개발자 포럼에 올렸던 글의 백업 파일입니다. 오래된 문서가 많아 현재 상황과 맞지 않을 수 있습니다.
서론
스프링데이터의 spring-boot-starter-data-elasticsearch 를 통해서 연동하는 방식이다. 필자는 아래 단점 때문에 좋아하지 않는 방식이다.
장점
- 간단하다.
- 정말 간단하다..
단점
- 버전에 민감하며, 버전이 올라갈 때 호환되지 않는 것들이 많다.
- 루씬계 복잡한 쿼리를 ORM에 끼워 맞추다 보니 셀릭트 작성하다? 왜이러지 이런느낌이 있다.
- 데이터 타입등이 자동화 된다.
구현
build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-data-elasticsearch:2.3.12.RELEASE")
AnimeDocument.kt
@Document(indexName = "anissia_anime")
data class AnimeDocument (
@Id
@Field(store = true)
var animeNo: Long = 0,
@Field(index = true)
var subject: String = "",
@Field(index = true)
var genres: List<String> = listOf(),
@Field(index = true)
var status: String = "",
@Field(index = true)
var translators: List<String> = listOf(),
@Field(index = true)
var endDate: Long = 0,
)
AnimeDocumentRepository.kt
interface AnimeDocumentRepository : ElasticsearchRepository<AnimeDocument, Long>, AnimeDocumentRepositoryCustom
interface AnimeDocumentRepositoryCustom {
fun search(cmd: SearchAnimeDocumentCommand): Page<Long>
}
class AnimeDocumentRepositoryCustomImpl(
private val operations: ElasticsearchOperations
): AnimeDocumentRepositoryCustom {
var log = As.logger<AnimeDocumentRepositoryCustomImpl>()
override fun search(cmd: SearchAnimeDocumentCommand): Page<Long> {
val keywords = cmd.keywords
val genres = cmd.genres
val translators = cmd.translators
val end = cmd.end
val pageable = cmd.pageable
val query = NativeQueryBuilder().withPageable(pageable)
if (keywords.isNotEmpty()) {
query.withQuery { q ->
q.wildcard { it.field("subject").wildcard(keywords.joinToString("*", "*", "*")) }
}
}
if (genres.isNotEmpty() || translators.isNotEmpty() || end) {
query.withFilter { filter ->
filter.bool { bq ->
bq.minimumShouldMatch("100%")
if (genres.isNotEmpty()) {
bq.filter { fs -> fs.match { it.field("genres").query(genres.joinToString(" ")).minimumShouldMatch("100%") } }
}
if (translators.isNotEmpty()) {
bq.filter { fs -> fs.match { it.field("translators").query(translators.joinToString(" ")) } }
}
if (end) {
bq.filter { fs -> fs.match { it.field("status").query("END") } }
}
bq
}
}
if (end) {
query.withSort { f -> f.field { fn -> fn.field("endDate").order(SortOrder.Desc) } }
}
}
return SearchHitSupport.searchPageFor(operations.search(query.build(), AnimeDocument::class.java), pageable).map { it.content.animeNo }
}
}