[Day 22] 实作 Database Plugin 整合 Exposed ORM, HikariCP 及 Flyway

Java Web 框架通常都至少整合一种 ORM,只要 Gradle depenency 加一下,再到设定档填入资料库连线设定即可。但目前 Ktor 官方尚未整合任何一种 ORM,Github 也没有找到套件,所以必须自己做 Database Plugin 调用 ORM API 进行整合。除了 ORM 之外,Connection Pool 及 Migration Tool 也是必要的基本功能,所以今天我先说明为何选择 Exposed ORM,然後再实作 Database Plugin 整合 Exposed, HikariCP, Flyway 至 Ktor

点我连结到完整的 Database Plugin 程序码

Exposed ORM

为什麽我选择 Exposed ORM 呢? 那是因为我从最早使用 Spring 搭配 Hibernate & JPA,到後来使用 Play Framework 搭配 Ebean,逐渐走向轻量化 Web 及 ORM 框架,所以对於 Ktor 要搭配那个 ORM,我想选择与 Ktor 一样由 JetBrains 开发的 100% Kotlin 及 typesafe SQL 的 Exposed。不要看到目前 Exposed 的版本号是 0.35.1 就认为还不成熟稳定,官方 2018 年在 github issue #359 回答「JebBrains 已经在 production 使用超过3年了」,所以换算今年 2021 已经超过6年了。

我觉得 Exposed 的优点是充分发挥 Kotlin 的特性,可以写出简洁 typesafe DSL 风格的 SQL,而且非常轻量易扩展。所以如果你使用 Kotlin 语言开发,又不需要用到一般 ORM 框架的进阶功能,不妨考虑一下 Exposed,我个人使用後非常推荐!

Database Plugin Configuration

我们先实作从外部设定档 application.conf 读取资料库连线设定

database {
    hikari {
        driverClassName = "org.postgresql.Driver"
        jdbcUrl = ${?DB_URL} #"jdbc:postgresql://localhost:5432/fanpoll"
        username = ${?DB_USER}
        password = ${?DB_PASSWORD}
        minimumIdle = 10
        maximumPoolSize = 20
        idleTimeout = 600000
        connectionTimeout = 10000
    }
    flyway {
        baselineOnMigrate = true
        validateOnMigrate = true
    }
}

对应的 config data class 如下,之後要再转为 com.zaxxer.hikari.HikariConfigorg.flywaydb.core.api.configuration.FluentConfiguration 物件。因为我只需要设定重要的属性值,所以就没有完整实作 HikariCP 及 Flyway 的所有设定了。

※ Exposed 在 2021-09-23 最新释出的 0.35.1 版本增加 DatabaseConfig 类别,往後我们可以更方便设定 org.jetbrains.exposed.sql.Database 物件

data class DatabaseConfig(
    val hikari: HikariConfig,
    val flyway: FlywayConfig,
)

data class HikariConfig(
    val driverClassName: String,
    val jdbcUrl: String,
    val username: String,
    val password: String,
    val minimumIdle: Int,
    val maximumPoolSize: Int,
    val idleTimeout: Long,
    val connectionTimeout: Long
)

data class FlywayConfig(
    val baselineOnMigrate: Boolean = true,
    val validateOnMigrate: Boolean = true,
    val table: String? = null
)

Init HikariCP and Exposed

Database Plugin 读取设定值後,先根据 HikariConfig 初始化 HikariDataSource,再呼叫 ExposedDatabase.connect(dataSource) 建立 org.jetbrains.exposed.sql.Database 物件即可开始操作资料库。最後不要忘记加上 KoinApplicationShutdownManager.register { closeConnection() },在停止 Server 时关闭资料库连线。

private lateinit var dataSource: HikariDataSource
private lateinit var defaultDatabase: org.jetbrains.exposed.sql.Database

private fun connect(config: com.zaxxer.hikari.HikariConfig) {
    try {
        logger.info("===== connect database ${config.jdbcUrl}... =====")
        dataSource = HikariDataSource(config)
        defaultDatabase = ExposedDatabase.connect(dataSource)
        logger.info("===== database connected =====")
    } catch (e: Throwable) {
        throw InternalServerException(
            InfraResponseCode.DB_ERROR,
            "fail to connect database connection pool! => ${config.jdbcUrl}", e
        )
    }
}

private fun closeConnection() {
    try {
        if (dataSource.isRunning) {
            logger.info("close database connection pool...")
            dataSource.close()
            logger.info("database connection pool closed")
        } else {
            logger.warn("database connection pool had been closed")
        }
    } catch (e: Throwable) {
        throw InternalServerException(InfraResponseCode.DB_ERROR, "could not close database connection pool", e)
    }
}

Flyway Migrate

当我们建立 HikariDataSource 物件之後,就可以根据 Flyway FluentConfiguration 物件建立 Flyway 物件,然後再呼叫 migrate() 方法执行 migrate 操作。总之有了 Flyway 物件之後,你就可以使用呼叫 API 的方式,执行 baseline, clean, undo…等指令。

private lateinit var flyway: Flyway

private fun migrate(config: FluentConfiguration) {
    try {
        logger.info("===== Flyway migrate... =====")
        flyway = config.dataSource(dataSource).load()
        flyway.migrate()
        logger.info("===== Flyway migrate finished =====")
    } catch (e: Throwable) {
        throw InternalServerException(InfraResponseCode.DB_ERROR, "fail to migrate database", e)
    }
}

完成结果


<<:  英文能力重要吗?

>>:  [Day 22] 筹码策略

Day-26 Process Synchronization

Process Synchronization tags: IT铁人 由於电脑同时会执行许多程序,不...

Day2 - numpy(1)基本介绍及使用

numpy介绍: 一个可操作高维度阵列的套件,可快速的对整个资料做运算。 就不多说了,让我们直接实际...

Day 19:1534. Count Good Triplets

今日题目 题目连结:1534. Count Good Triplets 题目主题:Array, En...

Day 6 ELK Stack on k8s 介绍

2021 铁人赛 DAY6 在上篇我们利用Prometheus捞取丛集内资源使用率的metric,再...

【JavaScript】阵列方法之filter()

【前言】 本系列为个人前端学习之路的学习笔记,在过往的学习过程中累积了很多笔记,如今想藉着IT邦帮忙...