Elasticsearch клиент для Kotlin: интеграция, примеры и возможности

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Elasticsearch клиент для Kotlin: полное руководство

Elasticsearch — это мощная распределённая поисковая система, основанная на Lucene. Для работы с ней из Kotlin-приложений используется официальный клиент Elasticsearch Java Client, который полностью совместим с Kotlin благодаря его близости к JVM. В этой статье мы разберём, как настроить клиент, выполнять основные операции (индексация, поиск, удаление) и использовать продвинутые возможности, такие как агрегации и фильтрация.

Подключение и настройка

Для начала добавьте зависимость в build.gradle.kts (или build.gradle):

// build.gradle.kts
dependencies {
    implementation("co.elastic.clients:elasticsearch-java:8.15.0")
    implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0")
}

Затем создайте клиент:

import co.elastic.clients.elasticsearch.ElasticsearchClient
import co.elastic.clients.json.jackson.JacksonJsonpMapper
import co.elastic.clients.transport.rest_client.RestClientTransport
import org.apache.http.HttpHost
import org.elasticsearch.client.RestClient

fun createClient(): ElasticsearchClient {
    val restClient = RestClient.builder(
        HttpHost("localhost", 9200, "http")
    ).build()

    val transport = RestClientTransport(restClient, JacksonJsonpMapper())
    return ElasticsearchClient(transport)
}

val client = createClient()

Основные операции

Индексация документа

Создайте data class для представления данных:

data class Product(
    val id: String,
    val name: String,
    val price: Double,
    val category: String
)

Индексация выполняется так:

suspend fun indexProduct(product: Product) {
    client.index { i ->
        i.index("products")
            .id(product.id)
            .document(product)
    }
}

// Пример вызова
val product = Product("1", "Ноутбук", 999.99, "Электроника")
runBlocking { indexProduct(product) }

Поиск по полям

suspend fun searchByName(name: String): List<Product> {
    val response = client.search<Product>({ s ->
        s.index("products")
            .query { q ->
                q.match { m ->
                    m.field("name")
                        .query(name)
                }
            }
    }, Product::class.java)

    return response.hits().hits().map { it.source()!! }
}

Удаление документа

suspend fun deleteProduct(id: String) {
    client.delete { d ->
        d.index("products")
            .id(id)
    }
}

Фильтрация и агрегации

Elasticsearch поддерживает сложные запросы. Например, поиск товаров в ценовом диапазоне с агрегацией по категориям:

suspend fun searchWithAggregation(minPrice: Double, maxPrice: Double) {
    val response = client.search<Product>({ s ->
        s.index("products")
            .query { q ->
                q.range { r ->
                    r.field("price")
                        .gte(minPrice.toString())
                        .lte(maxPrice.toString())
                }
            }
            .aggregations("by_category") { a ->
                a.terms { t ->
                    t.field("category")
                }
            }
    }, Product::class.java)

    // Получаем результаты агрегации
    val categories = response.aggregations()
        ?.get("by_category")
        ?.sterms()
        ?.buckets()
        ?.map { "${it.key()}: ${it.docCount()}" }
        ?: emptyList()

    println("Категории: $categories")
}

Пакетная обработка (Bulk API)

Для массовой индексации используйте bulk:

suspend fun bulkIndexProducts(products: List<Product>) {
    val bulkRequest = client.bulk { b ->
        products.forEach { product ->
            b.operations { op ->
                op.index { idx ->
                    idx.index("products")
                        .id(product.id)
                        .document(product)
                }
            }
        }
    }

    if (bulkRequest.errors()) {
        println("Ошибки при индексации: ${bulkRequest.items().filter { it.error() != null }}")
    } else {
        println("Успешно проиндексировано: ${products.size}")
    }
}

Работа с индексами

Создание индекса с маппингом:

suspend fun createIndexWithMapping() {
    client.indices().create { c ->
        c.index("products")
            .mappings { m ->
                m.properties("name") { p ->
                    p.text { t -> t.analyzer("standard") }
                }
                .properties("price") { p ->
                    p.double { d -> d }
                }
                .properties("category") { p ->
                    p.keyword { k -> k                    

Рекомендаций