Kotlin и PostgreSQL: пишем бэкенд быстрее, чем вы думали

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

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

Начать курс

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 или любого асинх

Блоги

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