Kotlin и PostgreSQL: неожиданный дуэт для вашего бэкенда
Когда речь заходит о современной разработке бэкенда, Kotlin и PostgreSQL всё чаще оказываются в одном предложении. И это не случайно. Kotlin привносит в JVM-мир лаконичность и функциональные фишки, а PostgreSQL остаётся эталоном надёжности и гибкости среди реляционных баз данных. В этом руководстве мы разберём, как подружить эти две технологии, напишем реальный код и рассмотрим лучшие практики.
Если вы уже знакомы с Kotlin для бэкенда или только начинаете — эта статья даст вам чёткий план действий. Поехали!
Почему Kotlin + PostgreSQL — это круто?
Kotlin — это язык, который заставляет влюбиться в себя с первых строк. Он совместим с Java, но при этом избавляет от тонн бойлерплейта. А PostgreSQL — это не просто «ещё одна база данных». Это мощный инструмент с поддержкой JSON, полнотекстового поиска, оконных функций и расширений вроде PostGIS.
Вместе они дают:
- Null-безопасность на уровне типов — Kotlin защищает вас от NPE при работе с результатами запросов.
- Асинхронность из коробки — корутины Kotlin идеально подходят для работы с пулами соединений.
- DSL для SQL — библиотеки вроде Exposed позволяют писать запросы как часть языка.
- Производительность — JVM + оптимизированный JDBC-драйвер дают отличную скорость.
Подготовка окружения: что нужно установить
Перед тем как писать код, убедитесь, что у вас есть:
- Установленный JDK 17+
- IntelliJ IDEA (Community Edition подойдёт)
- PostgreSQL 14+ (можно через Docker:
docker run --name pg -e POSTGRES_PASSWORD=pass -p 5432:5432 -d postgres) - Базовое понимание SQL
Создайте новый Gradle-проект в IntelliJ. В build.gradle.kts добавьте зависимости:
dependencies {
implementation("org.jetbrains.exposed:exposed-core:0.44.1")
implementation("org.jetbrains.exposed:exposed-dao:0.44.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.44.1")
implementation("org.postgresql:postgresql:42.7.1")
implementation("com.zaxxer:HikariCP:5.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
Соединение с PostgreSQL через HikariCP
Для работы с PostgreSQL в Kotlin нам понадобится пул соединений. HikariCP — стандарт де-факто. Он быстрый, лёгкий и настраивается за пару строк.
Создадим класс DatabaseFactory:
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.jetbrains.exposed.sql.Database
object DatabaseFactory {
fun init() {
val config = HikariConfig().apply {
jdbcUrl = "jdbc:postgresql://localhost:5432/yourdb"
driverClassName = "org.postgresql.Driver"
username = "postgres"
password = "pass"
maximumPoolSize = 10
isAutoCommit = false
transactionIsolation = "TRANSACTION_REPEATABLE_READ"
}
val dataSource = HikariDataSource(config)
Database.connect(dataSource)
}
}
Обратите внимание: isAutoCommit = false даёт нам контроль над транзакциями. Это важно для консистентности данных.
Создание таблиц с помощью Exposed ORM
Exposed — это легковесный ORM для Kotlin, который не навязывает свои правила. Вы можете работать как с DSL, так и с DAO-объектами.
Определим модель пользователя:
import org.jetbrains.exposed.sql.Table
object Users : Table("users") {
val id = integer("id").autoIncrement()
val name = varchar("name", 100)
val email = varchar("email", 255).uniqueIndex()
val age = integer("age").nullable()
override val primaryKey = PrimaryKey(id)
}
Теперь создадим таблицу в базе:
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
fun createTables() {
transaction {
SchemaUtils.create(Users)
}
}
Запустите DatabaseFactory.init() и createTables() — таблица users появится в вашей БД.
CRUD-операции: вставка, чтение, обновление, удаление
Вставка данных (INSERT)
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction
fun insertUser(name: String, email: String, age: Int?) {
transaction {
Users.insert {
it[Users.name] = name
it[Users.email] = email
it[Users.age] = age
}
}
}
Чтение данных (SELECT)
import org.jetbrains.exposed.sql.selectAll
fun getAllUsers(): List<User> {
return transaction {
Users.selectAll().map { row ->
User(
id = row[Users.id],
name = row[Users.name],
email = row[Users.email],
age = row[Users.age]
)
}
}
}
data class User(val id: Int, val name: String, val email: String, val age: Int?)
Обновление и удаление
import org.jetbrains.exposed.sql.update
import org.jetbrains.exposed.sql.deleteWhere
fun updateUserEmail(id: Int, newEmail: String) {
transaction {
Users.update({ Users.id eq id }) {
it[email] = newEmail
}
}
}
fun deleteUser(id: Int) {
transaction {
Users.deleteWhere { Users.id eq id }
}
}
Видите, как лаконично? Никаких строковых запросов, всё типизировано. Работа с БД в Kotlin через Exposed — это почти как писать на Python, но с безопасностью типов.
Асинхронность: корутины + PostgreSQL
В реальном приложении мы не хотим блокировать поток при каждом запросе к БД. Kotlin-корутины решают эту проблему. Но учтите: JDBC — синхронный, поэтому мы используем withContext(Dispatchers.IO).
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.exposed.sql.transactions.transaction
suspend fun getUsersAsync(): List<User> = withContext(Dispatchers.IO) {
transaction {
Users.selectAll().map { row ->
User(row[Users.id], row[Users.name], row[Users.email], row[Users.age])
}
}
}
Теперь вы можете вызывать эту функцию из корутины, не блокируя главный поток. Идеально для Ktor, Spring WebFlux или любого асинх