Введение: Зачем Kotlin для работы с базами данных?
Kotlin — современный статически типизированный язык, который полностью совместим с Java и активно используется для создания бэкенд-приложений. Благодаря лаконичному синтаксису, null-безопасности и мощным инструментам, Kotlin идеально подходит для работы с реляционными (PostgreSQL) и документо-ориентированными (MongoDB) базами данных. В этой статье мы разберём практические примеры подключения, выполнения CRUD-операций и оптимизации запросов.
Мы рассмотрим две популярные библиотеки: Exposed (для PostgreSQL) и KMongo (для MongoDB). Вы научитесь настраивать окружение, писать эффективные запросы и избегать типичных ошибок.
1. Работа с PostgreSQL через Exposed
Exposed — это ORM-библиотека для Kotlin, которая предоставляет два API: DSL (Domain-Specific Language) для написания запросов в стиле SQL и DAO (Data Access Object) для объектно-ориентированного подхода. Рассмотрим оба варианта.
1.1. Подключение и настройка
Добавьте зависимости в build.gradle.kts:
dependencies {
implementation("org.jetbrains.exposed:exposed-core:0.41.1")
implementation("org.jetbrains.exposed:exposed-dao:0.41.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.41.1")
implementation("org.postgresql:postgresql:42.5.0")
}
Подключение к базе данных:
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transaction
fun main() {
Database.connect(
url = "jdbc:postgresql://localhost:5432/mydb",
driver = "org.postgresql.Driver",
user = "user",
password = "password"
)
println("Подключение к PostgreSQL установлено")
}
1.2. Создание таблицы и CRUD-операции (DSL)
Определим таблицу Users:
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.update
object Users : Table() {
val id = integer("id").autoIncrement()
val name = varchar("name", 50)
val email = varchar("email", 100).uniqueIndex()
override val primaryKey = PrimaryKey(id)
}
Выполним CRUD-операции:
transaction {
// Создание таблицы
SchemaUtils.create(Users)
// INSERT
Users.insert {
it[name] = "Алексей"
it[email] = "alex@example.com"
}
// SELECT
val users = Users.selectAll().map {
"${it[Users.id]}: ${it[Users.name]} (${it[Users.email]})"
}
println(users)
// UPDATE
Users.update({ Users.id eq 1 }) {
it[name] = "Алексей Иванов"
}
// DELETE (используем deleteWhere)
Users.deleteWhere { Users.id eq 2 }
}
1.3. DAO-подход
Создадим сущность и DAO-класс:
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
object UsersDAO : IntIdTable() {
val name = varchar("name", 50)
val email = varchar("email", 100)
}
class User(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<User>(UsersDAO)
var name by UsersDAO.name
var email by UsersDAO.email
}
// Использование
transaction {
val newUser = User.new {
name = "Мария"
email = "maria@example.com"
}
println("Создан пользователь с ID: ${newUser.id.value}")
}
2. Работа с MongoDB через KMongo
KMongo — это корутин-ориентированный драйвер для MongoDB, построенный на основе официального Java-драйвера. Он поддерживает сериализацию через kotlinx.serialization и Jackson.
2.1. Подключение KMongo
Добавьте в build.gradle.kts:
dependencies {
implementation("org.litote.kmongo:kmongo-coroutine:4.7.0")
implementation("org.litote.kmongo:kmongo-serialization:4.7.0")
}
Подключение к MongoDB:
import org.litote.kmongo.coroutine.coroutine
import org.litote.kmongo.reactivestreams.KMongo
suspend fun main() {
val client = KMongo.createClient("mongodb://localhost:27017")
val database = client.getDatabase("mydb").coroutine
val collection = database.getCollection<User>("users")
println("Подключение к MongoDB установлено")
}
2.2. Определение модели и CRUD
Модель данных:
import org.bson.types.ObjectId
import kotlinx.serialization.Serializable
@Serializable
data class User(
@BsonId val id: ObjectId = ObjectId(),
val name: String,
val email: String,
val age: Int? = null
)
CRUD-операции с корутинами:
suspend fun main() {
val client = KMongo.createClient().coroutine
val db = client.getDatabase("testdb")
val users = db.getCollection<User>()
// CREATE
users.insertOne(User(name = "Иван", email = "ivan@test.com", age = 30))
// READ (все записи)
val allUsers: List<User> = users.find().toList()
println(allUsers)
// READ (с фильтром)
val filtered = users.find(User::name eq "Иван").toList()
// UPDATE
users.updateOne(
User::name eq "Иван",
set(User::age, 31)
)
// DELETE
users.deleteOne(User::email eq "ivan@test.com")
}
2.3. Агрегации и индексы
Создание индекса:
users.createIndex(User::email, unique = true)
Пример агрегации (группировка по возрасту):
val pipeline = listOf(
group(User::age, sumCount())
)
val result = users.aggregate<AggregationResult>(pipeline).toList()
3. Сравнение подходов и лучшие практики
Выбор между PostgreSQL и MongoDB зависит от структуры данных. Если вам нужны строгие схемы, связи и транзакции — выбирайте PostgreSQL с Exposed. Если данные слабо структурированы или часто