Skip to content

将数据库与 Kotlin、Ktor 和 Exposed 集成

代码示例: tutorial-server-db-integration

使用的插件:

Routing
Routing 是一个核心插件,用于处理服务器应用程序中的传入请求。
,
Static Content
了解如何提供静态内容,例如样式表、脚本、图像等。
,
Content Negotiation
ContentNegotiation 插件有两个主要目的:协商客户端和服务器之间的媒体类型,以及以特定格式序列化/反序列化内容。
,
Status pages
%plugin_name% 允许 Ktor 应用程序根据抛出的异常或状态码对任何失败状态做出适当响应。
,
kotlinx.serialization
ContentNegotiation 插件有两个主要目的:协商客户端和服务器之间的媒体类型,以及以特定格式序列化/反序列化内容。
, Exposed, Postgres

在本文中,您将学习如何使用 Kotlin 的 SQL 库 Exposed 将您的 Ktor 服务与关系数据库集成。

通过本教程,您将学习如何执行以下操作:

  • 创建使用 JSON 序列化的 RESTful 服务。
  • 将不同的版本库注入到这些服务中。
  • 使用伪造版本库为您的服务创建单元测试。
  • 使用 Exposed 和依赖项注入 (DI) 构建可用的版本库。
  • 部署访问外部数据库的服务。

在之前的教程中,我们使用任务管理器示例涵盖了基础知识,例如

处理请求
了解如何使用 Kotlin 和 Ktor 构建任务管理器应用程序,掌握路由、请求处理和参数的基础知识。
创建 RESTful API
了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含生成 JSON 文件的 RESTful API 示例。
使用 Thymeleaf 模板构建 Web 应用
了解如何使用 Kotlin、Ktor 和 Thymeleaf 模板构建网站。
。 虽然这些教程侧重于使用简单的内存中 TaskRepository 的前端功能, 本指南将重点转移到展示您的 Ktor 服务如何通过 Exposed SQL Library 与关系数据库交互。

尽管本指南更长、更复杂,但您仍将快速产出可用代码并逐步 引入新特性。

本指南将分为两部分:

  1. 使用内存中版本库创建您的应用程序。
  2. 将内存中版本库替换为使用 PostgreSQL 的版本库。

先决条件

您可以独立完成本教程,但是,我们建议您完成

创建 RESTful API
了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含生成 JSON 文件的 RESTful API 示例。
教程以熟悉 Content Negotiation 和 REST。

我们建议您安装 IntelliJ IDEA,但您也可以选择使用其他 IDE。

创建 RESTful 服务和内存中版本库

首先,您将重新创建任务管理器 RESTful 服务。最初,这将使用内存中 版本库,但您将构建一个设计,使其能够以最小的努力进行替换。

您将分六个阶段完成此操作:

  1. 创建初始项目。
  2. 添加起始代码。
  3. 添加 CRUD 路由。
  4. 添加单页应用程序 (SPA)。
  5. 手动测试应用程序。
  6. 添加自动化测试。

使用插件创建新项目

要使用 Ktor Project Generator 创建新项目,请按照以下步骤操作:

  1. 导航到 Ktor Project Generator

  2. Project artifact 字段中,输入 com.example.ktor-exposed-task-app 作为您的项目 artifact 名称。 在 Ktor Project Generator 中命名项目 artifact

  3. 在插件部分中,点击 Add 按钮搜索并添加以下插件:

    1. Routing
    2. Content Negotiation
    3. Kotlinx.serialization
    4. Static Content
    5. Status Pages
    6. Exposed
    7. Postgres

    在 Ktor Project Generator 中添加插件

  4. 添加插件后,点击插件部分右上角的 7 plugins 按钮以查看已添加的插件。

    您将看到一个包含所有将添加到您的项目的插件的列表: Ktor Project Generator 中的插件下拉列表

  5. 点击 Download 按钮以生成并下载您的 Ktor 项目。

  6. IntelliJ IDEA 或您选择的其他 IDE 中打开生成的项目。

  7. 导航到 src/main/kotlin/com/example 并删除文件 CitySchema.ktUsersSchema.kt

  8. 打开 Databases.kt 文件并删除 configureDatabases() 函数的内容。

    kotlin

    删除此功能的原因是 Ktor Project Generator 添加了示例 代码来演示如何将用户和城市数据持久化到 HSQLDB 或 PostgreSQL。 在本教程中您将不需要该示例代码。

添加起始代码

  1. 导航到 src/main/kotlin/com/example 并创建一个名为 model 的子包。
  2. model 包内,创建一个新文件 Task.kt
  3. 打开 Task.kt 并添加一个 enum 来表示优先级,以及一个 class 来表示 任务。

    kotlin

    Task 类使用

    kotlinx.serialization
    ContentNegotiation 插件有两个主要目的:协商客户端和服务器之间的媒体类型,以及以特定格式序列化/反序列化内容。
    库中的 Serializable 类型进行注解。

    与之前的教程一样,您将创建一个内存中版本库。然而,这次 版本库将实现一个 interface,以便您以后可以轻松替换它。

  4. model 子包中,创建一个新文件 TaskRepository.kt
  5. 打开 TaskRepository.kt 并添加以下 interface

    kotlin
  6. 在同一目录中创建一个新文件 FakeTaskRepository.kt
  7. 打开 FakeTaskRepository.kt 并添加以下 class

    kotlin

添加路由

  1. 打开 src/main/kotlin/com/example 中的 Serialization.kt 文件。
  2. 将现有的 Application.configureSerialization() 函数替换为以下 实现:

    kotlin

    这是您在

    创建 RESTful API
    了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含生成 JSON 文件的 RESTful API 示例。
    教程中实现的相同路由,不同之处在于您现在将版本库作为 参数传递给 routing() 函数。因为参数的类型是一个 interface, 所以可以注入许多不同的实现。

    现在您已向 configureSerialization() 添加了一个参数,现有的调用 将不再编译。幸运的是,此函数只被调用一次。

  3. 打开 src/main/kotlin/com/example 中的 Application.kt 文件。
  4. module() 函数替换为以下实现:

    kotlin

    您现在将 FakeTaskRepository 的一个实例注入到 configureSerialization() 中。 长期目标是能够将其替换为 PostgresTaskRepository

添加客户端页面

  1. 打开 src/main/resources/static 中的 index.html 文件。
  2. 将当前内容替换为以下实现:

    html

    这是之前教程中使用的同一个 SPA。由于它是用 JavaScript 编写的, 并且只使用浏览器中可用的库,您无需担心客户端 依赖项。

手动测试应用程序

    由于第一次迭代使用的是内存中版本库而不是连接到数据库, 您需要确保应用程序已正确配置。

  1. 导航到 src/main/resources/application.yaml 并删除 postgres 配置。

    yaml
  2. 在 IntelliJ IDEA 中,点击运行按钮 (intelliJ IDEA 运行图标) 启动应用程序。

  3. 在浏览器中导航到 http://0.0.0.0:8080/static/index.html 。您应该会看到客户端页面,其中包含三个表单和一个显示 过滤结果的表格。

    显示任务管理器客户端的浏览器窗口
  4. 通过填写并使用 Go 按钮发送表单来测试应用程序。 使用表格项上的 ViewDelete 按钮。

    显示任务管理器客户端的浏览器窗口

添加自动化单元测试

  1. 打开 src/test/kotlin/com/example 中的 ApplicationTest.kt ,并添加以下测试:

    kotlin

    为了使这些测试能够编译和运行,您需要在 Ktor Client 的 Content Negotiation 插件上添加依赖项。

  2. 打开 gradle/libs.versions.toml 文件并指定以下库:

    kotlin
  3. 打开 build.gradle.kts 并添加以下依赖项:

    kotlin
  4. 在 IntelliJ IDEA 中,点击编辑器右侧的 Gradle 通知图标 (intelliJ IDEA gradle 图标) 以加载 Gradle 更改。

  5. 在 IntelliJ IDEA 中,点击测试类定义旁边的运行按钮 (intelliJ IDEA 运行图标) 以运行测试。

    您应该会看到测试在 Run 窗格中成功运行。

    IntelliJ IDEA 的 Run 窗格中显示测试结果成功

添加 PostgreSQL 版本库

现在您有了一个使用内存中数据的可用应用程序,下一步是将数据 存储外部化到 PostgreSQL 数据库。

您将通过以下方式实现此目标:

  1. 在 PostgreSQL 中创建数据库 schema。
  2. 调整 TaskRepository 以进行异步访问。
  3. 在应用程序中配置数据库连接。
  4. Task 类型映射到关联的数据库表。
  5. 基于此映射创建一个新的版本库。
  6. 在启动代码中切换到这个新版本库。

创建数据库 schema

  1. 使用您选择的数据库管理工具,在 PostgreSQL 中创建一个新数据库。 名称无关紧要,只要您记住它即可。在此示例中,我们将使用 ktor_tutorial_db

    TIP

    有关 PostgreSQL 的更多信息,请参阅官方 文档

    在 IntelliJ IDEA 中,您可以使用数据库工具连接和管理您的 PostgreSQL 数据库。

  2. 对您的数据库运行以下 SQL 命令。这些命令将创建并填充 数据库 schema:

    sql

    请注意以下几点:

    • 您正在创建一个名为 task 的单表,其中包含 namedescriptionpriority 列。这些列需要映射到 Task 类的属性。
    • 如果表已存在,您将重新创建它,因此您可以重复运行脚本。
    • 还有一个名为 id 的额外列,其类型为 SERIAL。这将是一个整数值, 用于为每行提供其主键。这些值将由数据库为您分配。

调整现有版本库

    当对数据库执行查询时,最好让它们异步运行,以避免 阻塞处理 HTTP 请求的线程。在 Kotlin 中,这最好通过协程来管理。

  1. 打开 src/main/kotlin/com/example/model 中的 TaskRepository.kt 文件。

  2. 向所有接口方法添加 suspend 关键字:

    kotlin

    这将允许接口方法的实现者在不同的 Coroutine Dispatcher 上启动作业。

    您现在需要调整 FakeTaskRepository 的方法以 匹配,尽管在该实现中您不需要切换 Dispatcher。

  3. 打开 FakeTaskRepository.kt 文件并向所有方法添加 suspend 关键字:

    kotlin

    到目前为止,您没有引入任何新功能。相反,您已经为创建 PostgresTaskRepository 奠定了基础, 它将异步运行数据库查询。

配置数据库连接

    本教程的第一部分中,您删除了 Databases.kt 文件中 configureDatabases() 方法中的示例代码。您现在可以添加自己的实现。

  1. 打开 src/main/kotlin/com/example 中的 Databases.kt 文件。
  2. 使用 Database.connect() 函数连接到您的数据库,调整 设置的值以匹配您的环境:

    kotlin

    请注意,url 包含以下组件:

    • localhost:5432 是 PostgreSQL 数据库运行的主机和端口。
    • ktor_tutorial_db 是运行服务时创建的数据库的名称。

    TIP

    有关更多信息,请参阅 在 Exposed 中使用 Database 和 DataSource

创建对象/关系映射

  1. 导航到 src/main/kotlin/com/example 并创建一个名为 db 的新包。
  2. db 包内,创建一个新文件 mapping.kt
  3. 打开 mapping.kt 并添加 TaskTableTaskDAO 类型:

    kotlin

    这些类型使用 Exposed 库将 Task 类型中的属性映射到 数据库中 task 表的列。TaskTable 类型定义了基本映射,而 TaskDAO 类型添加了创建、查找、更新和删除任务的辅助方法。

    Ktor Project Generator 尚未添加对 DAO 类型的支持,因此您需要 在 Gradle 构建文件中添加相关依赖项。

  4. 打开 gradle/libs.versions.toml 文件并指定以下库:

    kotlin
  5. 打开 build.gradle.kts 文件并添加以下依赖项:

    kotlin
  6. 在 IntelliJ IDEA 中,点击编辑器右侧的 Gradle 通知图标 (intelliJ IDEA gradle 图标) 以加载 Gradle 更改。

  7. 导航回 mapping.kt 文件并添加以下两个辅助函数:

    kotlin

    suspendTransaction() 接受一段代码块,并通过 IO Dispatcher 在数据库 事务中运行它。这旨在将阻塞的作业卸载到线程池中。

    daoToModel()TaskDAO 类型的一个实例转换为 Task 对象。

  8. 添加以下缺失的 import:

    kotlin

编写新版本库

    您现在拥有创建数据库特定版本库所需的所有资源。

  1. 导航到 src/main/kotlin/com/example/model 并创建一个新文件 PostgresTaskRepository.kt
  2. 打开 PostgresTaskRepository.kt 文件并使用以下实现创建一个新类型:

    kotlin

    在此实现中,您使用 TaskDAOTaskTable 类型的辅助方法与 数据库交互。创建此新版本库后,唯一剩下的任务是在您的路由中切换到使用它。

切换到新版本库

    要切换到外部数据库,您只需更改版本库类型。

  1. 打开 src/main/kotlin/com/example 中的 Application.kt 文件。
  2. Application.module() 函数中,将 FakeTaskRepository 替换为 PostgresTaskRepository

    kotlin

    因为您通过接口注入依赖项,所以实现的切换对于管理路由的代码来说是透明的。

  3. 在 IntelliJ IDEA 中,点击重新运行按钮 (intelliJ IDEA 重新运行图标) 以重新启动应用程序。

  4. 导航到 http://0.0.0.0:8080/static/index.html。 UI 保持不变,但现在它从数据库中获取数据。
  5. 要验证这一点,请使用表单添加新任务,并查询 PostgreSQL 中 tasks 表中保存的数据。

    TIP

    在 IntelliJ IDEA 中,您可以使用查询 控制台SELECT SQL 语句来查询表数据:

    SQL

    查询后,数据应显示在 Service 窗格中,包括新任务:

    IntelliJ IDEA 的 Service 窗格中显示的任务表

至此,您已成功将数据库集成到您的应用程序中。

由于生产代码中不再需要 FakeTaskRepository 类型,您可以将其移至测试 源代码集,即 src/test/com/example

最终项目结构应如下所示:

IntelliJ IDEA 的 Project View 中显示的 src 文件夹

下一步

您现在有了一个与 Ktor RESTful 服务通信的应用程序。该服务又使用 Exposed 编写的 版本库来访问 PostgreSQL。您还拥有一套测试, 可以验证核心功能,而无需 Web 服务器或数据库。

此结构可以根据需要扩展以支持任意功能,但是,您 可能首先需要考虑设计的非功能性方面,例如容错性、安全性以及 可伸缩性。您可以从将数据库设置提取

配置文件
了解如何在配置文件中配置各种服务器参数。
开始。