将数据库与 Kotlin、Ktor 和 Exposed 集成
代码示例: tutorial-server-db-integration
使用的插件:
在本文中,您将学习如何使用 Kotlin 的 SQL 库 Exposed 将您的 Ktor 服务与关系数据库集成。
通过本教程,您将学习如何执行以下操作:
- 创建使用 JSON 序列化的 RESTful 服务。
- 将不同的版本库注入到这些服务中。
- 使用伪造版本库为您的服务创建单元测试。
- 使用 Exposed 和依赖项注入 (DI) 构建可用的版本库。
- 部署访问外部数据库的服务。
在之前的教程中,我们使用任务管理器示例涵盖了基础知识,例如
TaskRepository
的前端功能, 本指南将重点转移到展示您的 Ktor 服务如何通过 Exposed SQL Library 与关系数据库交互。 尽管本指南更长、更复杂,但您仍将快速产出可用代码并逐步 引入新特性。
本指南将分为两部分:
先决条件
您可以独立完成本教程,但是,我们建议您完成
我们建议您安装 IntelliJ IDEA,但您也可以选择使用其他 IDE。
创建 RESTful 服务和内存中版本库
首先,您将重新创建任务管理器 RESTful 服务。最初,这将使用内存中 版本库,但您将构建一个设计,使其能够以最小的努力进行替换。
您将分六个阶段完成此操作:
使用插件创建新项目
要使用 Ktor Project Generator 创建新项目,请按照以下步骤操作:
导航到 Ktor Project Generator 。
在 Project artifact 字段中,输入 com.example.ktor-exposed-task-app 作为您的项目 artifact 名称。
在插件部分中,点击 Add 按钮搜索并添加以下插件:
- Routing
- Content Negotiation
- Kotlinx.serialization
- Static Content
- Status Pages
- Exposed
- Postgres
添加插件后,点击插件部分右上角的 7 plugins 按钮以查看已添加的插件。
您将看到一个包含所有将添加到您的项目的插件的列表:
点击 Download 按钮以生成并下载您的 Ktor 项目。
在 IntelliJ IDEA 或您选择的其他 IDE 中打开生成的项目。
导航到 src/main/kotlin/com/example 并删除文件 CitySchema.kt 和 UsersSchema.kt 。
打开 Databases.kt 文件并删除
configureDatabases()
函数的内容。kotlin删除此功能的原因是 Ktor Project Generator 添加了示例 代码来演示如何将用户和城市数据持久化到 HSQLDB 或 PostgreSQL。 在本教程中您将不需要该示例代码。
添加起始代码
- 导航到 src/main/kotlin/com/example 并创建一个名为 model 的子包。
- 在 model 包内,创建一个新文件 Task.kt 。
打开 Task.kt 并添加一个
enum
来表示优先级,以及一个class
来表示 任务。kotlinTask
类使用kotlinx.serialization库中的ContentNegotiation 插件有两个主要目的:协商客户端和服务器之间的媒体类型,以及以特定格式序列化/反序列化内容。Serializable
类型进行注解。与之前的教程一样,您将创建一个内存中版本库。然而,这次 版本库将实现一个
interface
,以便您以后可以轻松替换它。- 在 model 子包中,创建一个新文件 TaskRepository.kt 。
打开 TaskRepository.kt 并添加以下
interface
:kotlin- 在同一目录中创建一个新文件 FakeTaskRepository.kt 。
打开 FakeTaskRepository.kt 并添加以下
class
:kotlin
添加路由
- 打开 src/main/kotlin/com/example 中的 Serialization.kt 文件。
将现有的
Application.configureSerialization()
函数替换为以下 实现:kotlin这是您在
创建 RESTful API教程中实现的相同路由,不同之处在于您现在将版本库作为 参数传递给了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含生成 JSON 文件的 RESTful API 示例。routing()
函数。因为参数的类型是一个interface
, 所以可以注入许多不同的实现。现在您已向
configureSerialization()
添加了一个参数,现有的调用 将不再编译。幸运的是,此函数只被调用一次。- 打开 src/main/kotlin/com/example 中的 Application.kt 文件。
将
module()
函数替换为以下实现:kotlin您现在将
FakeTaskRepository
的一个实例注入到configureSerialization()
中。 长期目标是能够将其替换为PostgresTaskRepository
。
添加客户端页面
- 打开 src/main/resources/static 中的 index.html 文件。
将当前内容替换为以下实现:
html这是之前教程中使用的同一个 SPA。由于它是用 JavaScript 编写的, 并且只使用浏览器中可用的库,您无需担心客户端 依赖项。
手动测试应用程序
导航到 src/main/resources/application.yaml 并删除
postgres
配置。yaml在 IntelliJ IDEA 中,点击运行按钮 (
) 启动应用程序。
在浏览器中导航到 http://0.0.0.0:8080/static/index.html 。您应该会看到客户端页面,其中包含三个表单和一个显示 过滤结果的表格。
通过填写并使用 Go 按钮发送表单来测试应用程序。 使用表格项上的 View 和 Delete 按钮。
由于第一次迭代使用的是内存中版本库而不是连接到数据库, 您需要确保应用程序已正确配置。
添加自动化单元测试
打开 src/test/kotlin/com/example 中的 ApplicationTest.kt ,并添加以下测试:
kotlin为了使这些测试能够编译和运行,您需要在 Ktor Client 的 Content Negotiation 插件上添加依赖项。
打开 gradle/libs.versions.toml 文件并指定以下库:
kotlin打开 build.gradle.kts 并添加以下依赖项:
kotlin在 IntelliJ IDEA 中,点击编辑器右侧的 Gradle 通知图标 (
) 以加载 Gradle 更改。
在 IntelliJ IDEA 中,点击测试类定义旁边的运行按钮 (
) 以运行测试。
您应该会看到测试在 Run 窗格中成功运行。
添加 PostgreSQL 版本库
现在您有了一个使用内存中数据的可用应用程序,下一步是将数据 存储外部化到 PostgreSQL 数据库。
您将通过以下方式实现此目标:
- 在 PostgreSQL 中创建数据库 schema。
- 调整
TaskRepository
以进行异步访问。 - 在应用程序中配置数据库连接。
- 将
Task
类型映射到关联的数据库表。 - 基于此映射创建一个新的版本库。
- 在启动代码中切换到这个新版本库。
创建数据库 schema
使用您选择的数据库管理工具,在 PostgreSQL 中创建一个新数据库。 名称无关紧要,只要您记住它即可。在此示例中,我们将使用 ktor_tutorial_db 。
对您的数据库运行以下 SQL 命令。这些命令将创建并填充 数据库 schema:
sql请注意以下几点:
- 您正在创建一个名为 task 的单表,其中包含 name 、 description 和 priority 列。这些列需要映射到
Task
类的属性。 - 如果表已存在,您将重新创建它,因此您可以重复运行脚本。
- 还有一个名为 id 的额外列,其类型为
SERIAL
。这将是一个整数值, 用于为每行提供其主键。这些值将由数据库为您分配。
- 您正在创建一个名为 task 的单表,其中包含 name 、 description 和 priority 列。这些列需要映射到
调整现有版本库
打开 src/main/kotlin/com/example/model 中的 TaskRepository.kt 文件。
向所有接口方法添加
suspend
关键字:kotlin这将允许接口方法的实现者在不同的 Coroutine Dispatcher 上启动作业。
您现在需要调整
FakeTaskRepository
的方法以 匹配,尽管在该实现中您不需要切换 Dispatcher。打开 FakeTaskRepository.kt 文件并向所有方法添加
suspend
关键字:kotlin到目前为止,您没有引入任何新功能。相反,您已经为创建
PostgresTaskRepository
奠定了基础, 它将异步运行数据库查询。
当对数据库执行查询时,最好让它们异步运行,以避免 阻塞处理 HTTP 请求的线程。在 Kotlin 中,这最好通过协程来管理。
配置数据库连接
- 打开 src/main/kotlin/com/example 中的 Databases.kt 文件。
使用
Database.connect()
函数连接到您的数据库,调整 设置的值以匹配您的环境:kotlin请注意,
url
包含以下组件:localhost:5432
是 PostgreSQL 数据库运行的主机和端口。ktor_tutorial_db
是运行服务时创建的数据库的名称。
TIP
有关更多信息,请参阅 在 Exposed 中使用 Database 和 DataSource。
在本教程的第一部分中,您删除了 Databases.kt 文件中 configureDatabases()
方法中的示例代码。您现在可以添加自己的实现。
创建对象/关系映射
- 导航到 src/main/kotlin/com/example 并创建一个名为 db 的新包。
- 在 db 包内,创建一个新文件 mapping.kt 。
打开 mapping.kt 并添加
TaskTable
和TaskDAO
类型:kotlin这些类型使用 Exposed 库将
Task
类型中的属性映射到 数据库中 task 表的列。TaskTable
类型定义了基本映射,而TaskDAO
类型添加了创建、查找、更新和删除任务的辅助方法。Ktor Project Generator 尚未添加对 DAO 类型的支持,因此您需要 在 Gradle 构建文件中添加相关依赖项。
打开 gradle/libs.versions.toml 文件并指定以下库:
kotlin打开 build.gradle.kts 文件并添加以下依赖项:
kotlin在 IntelliJ IDEA 中,点击编辑器右侧的 Gradle 通知图标 (
) 以加载 Gradle 更改。
导航回 mapping.kt 文件并添加以下两个辅助函数:
kotlinsuspendTransaction()
接受一段代码块,并通过 IO Dispatcher 在数据库 事务中运行它。这旨在将阻塞的作业卸载到线程池中。daoToModel()
将TaskDAO
类型的一个实例转换为Task
对象。添加以下缺失的 import:
kotlin
编写新版本库
- 导航到 src/main/kotlin/com/example/model 并创建一个新文件 PostgresTaskRepository.kt 。
打开 PostgresTaskRepository.kt 文件并使用以下实现创建一个新类型:
kotlin在此实现中,您使用
TaskDAO
和TaskTable
类型的辅助方法与 数据库交互。创建此新版本库后,唯一剩下的任务是在您的路由中切换到使用它。
您现在拥有创建数据库特定版本库所需的所有资源。
切换到新版本库
- 打开 src/main/kotlin/com/example 中的 Application.kt 文件。
在
Application.module()
函数中,将FakeTaskRepository
替换为PostgresTaskRepository
:kotlin因为您通过接口注入依赖项,所以实现的切换对于管理路由的代码来说是透明的。
在 IntelliJ IDEA 中,点击重新运行按钮 (
) 以重新启动应用程序。
- 导航到 http://0.0.0.0:8080/static/index.html。 UI 保持不变,但现在它从数据库中获取数据。
要验证这一点,请使用表单添加新任务,并查询 PostgreSQL 中 tasks 表中保存的数据。
要切换到外部数据库,您只需更改版本库类型。
至此,您已成功将数据库集成到您的应用程序中。
由于生产代码中不再需要 FakeTaskRepository
类型,您可以将其移至测试 源代码集,即 src/test/com/example 。
最终项目结构应如下所示:

下一步
您现在有了一个与 Ktor RESTful 服务通信的应用程序。该服务又使用 Exposed 编写的 版本库来访问 PostgreSQL。您还拥有一套测试, 可以验证核心功能,而无需 Web 服务器或数据库。
此结构可以根据需要扩展以支持任意功能,但是,您 可能首先需要考虑设计的非功能性方面,例如容错性、安全性以及 可伸缩性。您可以从将数据库设置提取到