新闻详情

新闻详情

首页 / 资讯中心 / 详情

Exposed DAO API 快速上手:用 Kotlin 面向对象风格完成建表与 CRUD 的完整教程

发布时间:2026/9/26 14:20:02来源:尧图网络
Exposed DAO API 快速上手:用 Kotlin 面向对象风格完成建表与 CRUD 的完整教程
ORM后端数据存储【免费下载链接】ExposedKotlin SQL Framework项目地址https://gitcode.com/gh_mirrors/ex/Exposed点击查看免费下载本文是一份以 JetBrains ExposedKotlin SQL FrameworkDAO API 为核心的上手指南全程围绕一个可运行的简单控制台应用展开你将学会配置内存数据库连接、用IntIdTable定义表、用IntEntity定义实体并在事务中完成 Create、Read、Update、Delete 全流程操作。读完本文你将掌握 Exposed DAO 的核心开发模式并能在自己的 Kotlin 项目中直接迁移这套写法。文中所涉及的完整示例代码位于 documentation-website/Writerside/snippets/get-started-with-exposed-dao可在仓库中对照阅读。前置准备开始之前请确保你的机器上具备以下环境Gradle最新发行版用于初始化与管理项目构建JDK版本 8 或更高IDE如 IntelliJ IDEA。官方推荐 IntelliJ IDEA Ultimate其内置数据库工具并支持 Exposed 插件可提供代码补全、检查以及根据已有 schema 生成表与实体的能力。创建新项目在终端中创建一个新目录并进入mkdir exposed-dao-kotlin-app cd exposed-dao-kotlin-app运行gradle init初始化 Gradle 项目gradle init按提示选择以下选项项目类型选择1: Application实现语言选择2: Kotlin其余问题直接回车使用默认值即可。初始化完成后在 IDE 中打开项目IntelliJ IDEA 可直接执行idea .。添加依赖使用 Exposed 前需要为项目添加依赖。首先打开gradle/libs.versions.toml定义 Exposed 与 H2 的版本和构件当前仓库自身的版本声明可参考 gradle/libs.versions.toml[versions] //... exposed %exposed_version% h2 %h2_db_version% [libraries] //... exposed-core { module org.jetbrains.exposed:exposed-core, version.ref exposed } exposed-dao { module org.jetbrains.exposed:exposed-dao, version.ref exposed } exposed-jdbc { module org.jetbrains.exposed:exposed-jdbc, version.ref exposed } h2 { module com.h2database:h2, version.ref h2 }三个 Exposed 构件各有分工exposed-core提供以类型安全方式操作数据库所需的基础组件与抽象包含 DSL APIexposed-dao提供 Data Access ObjectDAOAPI即本文主角exposed-jdbcexposed-core的扩展增加 Java Database ConnectivityJDBC支持。然后打开app/build.gradle.kts在dependencies块中加入这些模块dependencies { //... implementation(libs.exposed.core) implementation(libs.exposed.dao) implementation(libs.exposed.jdbc) implementation(libs.h2) //... }修改完构建脚本后在 IntelliJ IDEA 中点击编辑器右侧的 Gradle 通知图标加载 Gradle 变更。配置数据库连接使用 Exposed 访问数据库时第一步是获取连接并创建事务。连接配置通过Database.connect()完成。打开app/src/main/kotlin/org/example/App.kt写入如下实现package org.example import org.jetbrains.exposed.v1.jdbc.Database fun main() { Database.connect(jdbc:h2:mem:test, driver org.h2.Driver) // ... }Database.connect()创建了一个代表数据库的实例这里传入了连接 URL 与驱动两个参数。逐段拆解 URLjdbc:h2:mem:testjdbc表示这是一个 JDBC 连接h2表示目标数据库是 H2mem表示数据库运行在内存中数据只存在于内存应用停止即丢失test数据库名称。org.h2.Driver指定用于建立连接的 H2 JDBC 驱动。两个值得注意的行为惰性连接调用Database.connect()只配置连接设置并不会立即建立数据库连接真正的连接在首次执行数据库操作时才建立自动注册默认情况下 Exposed 会自动注册该数据库连接如需改变这一行为可在调用Database.connect()时设置connectionAutoRegistration参数。从源码结构看Database.connect()在 Database.kt 中提供了多个重载支持传入 URL、驱动、数据源DataSource等不同形式的连接配置便于在真实项目中接入连接池。至此你已为 Kotlin 项目添加了 Exposed 并配置好数据库连接接下来可以定义数据模型并使用 DAO API 与数据库交互。定义表对象Exposed 的 DAO API 提供了基类IdTable及其子类用于定义以标准id列作为主键的表。在app/src/main/kotlin/org/example/目录下新建Task.kt文件添加表定义package org.example import org.jetbrains.exposed.v1.core.dao.id.IntIdTable object Tasks : IntIdTable(tasks) { val title varchar(name, 128) val description varchar(description, 128) val isCompleted bool(completed).default(false) }在IntIdTable构造函数中传入名称tasks即为表配置了自定义名称。如果不提供名称Exposed 会根据对象名推导表名这可能因命名规范不同而产生意外结果。Tasks对象定义了以下列title与descriptionString类型列使用varchar()创建每列最大长度 128 字符isCompletedBoolean类型列使用bool()定义并通过default(false)将默认值配置为false。IntIdTable会自动为表添加一个自增整数id列作为主键。这一点在源码中有直接体现见 IdTable.ktIntIdTable的内部实现即为final override val id: ColumnEntityIDInt integer(columnName).autoIncrement(sequenceName).entityId() final override val primaryKey PrimaryKey(id)也就是说integer()定义 4 字节整型列、autoIncrement()声明自增可传sequenceName指定序列名、entityId()将其包装为EntityIDInt并最终声明为表的主键。到这里你已经定义了一个带列的表相当于为tasks表创建了蓝图。定义实体使用 DAO 方式时每个基于IntIdTable定义的表都必须关联一个对应的实体类实体类详细说明见 DAO-Entity-definition.topic。实体类代表表中的单条记录由主键唯一标识。继续完善Task.ktpackage org.example import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntityClass object Tasks : IntIdTable(tasks) { val title varchar(name, 128) val description varchar(description, 128) val isCompleted bool(completed).default(false) } class Task(id: EntityIDInt) : IntEntity(id) { companion object : IntEntityClassTask(Tasks) var title by Tasks.title var description by Tasks.description var isCompleted by Tasks.isCompleted override fun toString(): String { return Task(id$id, title$title, completed$isCompleted) } }逐项理解这段代码Task继承IntEntity它是主键类型为Int的实体基类。源码定义见 IntEntity.ktabstract class IntEntity(id: EntityIDInt) : EntityInt(id)构造函数中的EntityIDInt参数代表该实体所映射的数据库行的主键companion object继承IntEntityClassTask将实体类与Tasks表绑定同时承担查询入口的职责源码见 IntEntity.kttitle、description、isCompleted三个属性通过 Kotlin 的by关键字委托给Tasks表中的对应列toString()自定义了Task实例的字符串表示便于调试与日志输出打印结果将包含实体 ID、标题与完成状态。创建和查询表借助 DAO API你可以用类型安全、面向对象的方式操作数据库就像操作普通 Kotlin 类一样。所有数据库操作都必须运行在事务内事务由Transaction类的实例表示你可以在其 lambda 中定义和操作数据Exposed 会在后台自动管理事务的打开与关闭事务的完整说明见 Transactions.md。打开App.kt加入事务函数package org.example import org.jetbrains.exposed.v1.core.StdOutSqlLogger import org.jetbrains.exposed.v1.jdbc.Database import org.jetbrains.exposed.v1.jdbc.SchemaUtils import org.jetbrains.exposed.v1.jdbc.transactions.transaction fun main() { Database.connect(jdbc:h2:mem:test, driver org.h2.Driver) transaction { addLogger(StdOutSqlLogger) SchemaUtils.create(Tasks) val task1 Task.new { title Learn Exposed DAO description Follow the DAO tutorial } val task2 Task.new { title Read The Hobbit description Read chapter one isCompleted true } println(Created new tasks with ids ${task1.id} and ${task2.id}) val completed Task.find { Tasks.isCompleted eq true }.toList() println(Completed tasks: ${completed.count()}) } }首先使用SchemaUtils.create()创建tasks表。SchemaUtils对象持有创建、修改和删除数据库对象的工具方法。表创建完成后使用IntEntityClass的扩展方法.new()添加两条新的Task记录val task1 Task.new { title Learn Exposed DAO description Follow the DAO tutorial } val task2 Task.new { title Read The Hobbit description Read chapter one isCompleted true }从源码结构看new()在 EntityClass.kt 中定义接受一个以实体为接收者的初始化 lambdaExposed 会在插入后把生成的主键回填到实体的id属性上。示例中task1、task2都是Task实体的实例各自代表Tasks表中的新行在new块内为各列赋值后Exposed 会翻译为如下 SQLINSERT INTO TASKS (name, DESCRIPTION, COMPLETED) VALUES (Learn Exposed DAO, Follow the DAO tutorial, FALSE) INSERT INTO TASKS (name, DESCRIPTION, COMPLETED) VALUES (Read The Hobbit, Read chapter one, TRUE)随后用.find()执行带条件的查询检索所有isCompleted为true的任务val completed Task.find { Tasks.isCompleted eq true }.toList() println(Completed tasks: ${completed.count()})find {}接收一个类型安全的条件 lambdaTasks.isCompleted eq true返回可迭代的结果集toList()触发实际查询执行。注意 DAO 查询是惰性的——查询不会立即执行直到你调用遍历结果的方法如toList()、forEach()时才真正发起 SQL。在测试代码之前最好能看到 Exposed 发送给数据库的 SQL 语句为此需要添加日志。启用日志在transaction块的开头加入如下代码以启用 SQL 查询日志transaction { addLogger(StdOutSqlLogger) SchemaUtils.create(Tasks) // ... }addLogger(StdOutSqlLogger)将 SQL 语句输出到标准输出stdout方便观察 Exposed 实际生成的 DDL 与 DML。运行应用在 IntelliJ IDEA 中点击 gutter 区域的运行按钮启动应用应用将在底部Run工具窗口启动你可以在那里看到 SQL 日志与打印结果SQL: SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME MODE SQL: CREATE TABLE IF NOT EXISTS TASKS (ID INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(128) NOT NULL, DESCRIPTION VARCHAR(128) NOT NULL, COMPLETED BOOLEAN DEFAULT FALSE NOT NULL) SQL: INSERT INTO TASKS (name, DESCRIPTION, COMPLETED) VALUES (Learn Exposed DAO, Follow the DAO tutorial, FALSE) SQL: INSERT INTO TASKS (name, DESCRIPTION, COMPLETED) VALUES (Read The Hobbit, Read chapter one, TRUE) Created new tasks with ids 1 and 2 SQL: SELECT TASKS.ID, TASKS.name, TASKS.DESCRIPTION, TASKS.COMPLETED FROM TASKS WHERE TASKS.COMPLETED TRUE Completed tasks: 1对照输出可以看出SchemaUtils.create(Tasks)生成了CREATE TABLE IF NOT EXISTS两次Task.new分别产生 INSERTTask.find生成了带WHERE TASKS.COMPLETED TRUE的 SELECT。这也验证了 DAO 实体到 SQL 的映射关系。更新和删除任务下面扩展应用功能更新并删除任务。在同一个transaction()函数中追加代码对应 snippets 中的 App.kt// Update task1.title Try Exposed DAO task1.isCompleted true println(Updated task1: $task1) // Delete task2.delete() println(Remaining tasks: ${Task.all().toList()})更新实体属性就像修改普通 Kotlin 类的属性一样简单直接对task1.title和task1.isCompleted赋值即可。删除则调用实体的.delete()方法task2.delete()会将该行从数据库中移除。关键机制延迟刷新lazy flush。Exposed 在修改实体属性如task1.title时并不会立即发出UPDATE语句而是将改动缓存在内存中在下一次读操作之前或事务结束时统一刷新到数据库。因此你会在日志中看到这样的输出顺序SQL: UPDATE TASKS SET COMPLETEDTRUE, nameTry Exposed DAO WHERE ID 1重启应用IntelliJ IDEA 中点击 rerun 按钮后应看到以下完整结果SQL: SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME MODE SQL: CREATE TABLE IF NOT EXISTS TASKS (ID INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(128) NOT NULL, DESCRIPTION VARCHAR(128) NOT NULL, COMPLETED BOOLEAN DEFAULT FALSE NOT NULL) SQL: INSERT INTO TASKS (name, DESCRIPTION, COMPLETED) VALUES (Learn Exposed DAO, Follow the DAO tutorial, FALSE) SQL: INSERT INTO TASKS (name, DESCRIPTION, COMPLETED) VALUES (Read The Hobbit, Read chapter one, TRUE) Created new tasks with ids 1 and 2 SQL: SELECT TASKS.ID, TASKS.name, TASKS.DESCRIPTION, TASKS.COMPLETED FROM TASKS WHERE TASKS.COMPLETED TRUE Completed tasks: 1 Updated task1: Task(id1, titleTry Exposed DAO, completedtrue) SQL: UPDATE TASKS SET COMPLETEDTRUE, nameTry Exposed DAO WHERE ID 1 SQL: DELETE FROM TASKS WHERE TASKS.ID 2 SQL: SELECT TASKS.ID, TASKS.name, TASKS.DESCRIPTION, TASKS.COMPLETED FROM TASKS Remaining tasks: [Task(id1, titleTry Exposed DAO, completedtrue)]注意日志中Updated task1: Task(id1, ...)打印在 UPDATE 之前这正是因为toString()读取的是内存中尚未刷新的属性值随后 Exposed 才在下次读取Task.all()前把改动 flush 到数据库。最后Task.all()列出表中全部剩余任务确认task2已被删除。关于内存数据库的一个提醒如果在此之后打开第二个事务你可能会发现表和数据都消失了——即使应用还没有停止。这是 H2 内存数据库在管理连接与事务时的预期行为。若希望数据库保持打开可在 URL 中追加DB_CLOSE_DELAY-1Database.connect(jdbc:h2:mem:test;DB_CLOSE_DELAY-1, driver org.h2.Driver)下一步继续深入 DAO API你已经用 Exposed 的 DAO API 构建了一个简单的控制台应用完成了内存数据库中的建表、插入、查询、更新与删除。接下来可以从两个方向继续深入学习完整的 CRUD 操作掌握批量操作、复杂查询等进阶用法学习如何在实体间定义关系一对一、一对多、多对多等。这些章节将帮助你用 Exposed 类型安全、面向对象的方式构建更复杂、更贴近真实业务的数据模型。赞分享ORM后端数据存储【免费下载链接】ExposedKotlin SQL Framework项目地址https://gitcode.com/gh_mirrors/ex/Exposed点击查看免费下载相关推荐数据科学走进现实世界Data-Science-For-Beginners 课程第 20 课“真实世界应用”全景解读数据科学走进现实世界Data Science For Beginners 课程第 20 课“真实世界应用”全景解读 本篇以 Data Science ForORM后端数据存储如何快速上手WeUI5分钟搭建微信风格界面的完整教程如何快速上手WeUI5分钟搭建微信风格界面的完整教程 WeUI是微信官方设计团队专门为微信网页开发量身打造的UI框架能够帮助开发者快速构建与微信原生体验一致UI组件前端如何快速上手TiDB面向新手的完整使用教程如何快速上手TiDB面向新手的完整使用教程 TiDB是一个开源的分布式NewSQL数据库它结合了传统关系型数据库的最佳特性和NoSQL系统的可扩展性。作为一上一篇为什么选择Toga5大理由让你爱上这个Python GUI工具包下一篇PingFangSC字体跨平台解决方案实现苹果原生字体体验的统一技术架构创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

毕业论文写作全流程指南:从初稿到图表格式与AI率优化 2026/9/26 14:19:56

毕业论文写作全流程指南:从初稿到图表格式与AI率优化

又是一年论文季,宿舍楼里最常听见的对话已经从“今天吃什么”变成了“你初稿写到哪了”“图表画了吗”“格式调完没”,以及一个近几年才出现的新问题:“你 AI 率降到多少了”。这四个问题,几乎是每个毕业生在提交论文前绕不开的四…

阅读更多 →
从家纺到睡眠科技:未来25年床品行业的材料与智能变革 2026/9/26 14:19:56

从家纺到睡眠科技:未来25年床品行业的材料与智能变革

这个标题看着很大,但其实拆开看就三件事: 技术怎么变、材料怎么变、人怎么花钱 。我在家纺行业摸爬滚打这些年,亲眼看着床品从"一块布"变成了"一套睡眠系统",从"纯棉四件套"卷到"智能温控被…

阅读更多 →
Python Django校园点餐系统全解析:从数据库设计到部署实战 2026/9/26 14:19:56

Python Django校园点餐系统全解析:从数据库设计到部署实战

1. 项目里到底有什么:系统全貌与需求拆解 先别急着看代码。拿到任何一套"Python Django校园食堂点餐系统"的源码包,第一件事不是跑起来,而是把这个压缩包里的东西当成一个完整业务项目来理解。这个系统听起来就是个课设/毕设级别的…

阅读更多 →
Redis内存优化:Hash为何比String省内存?底层原理与生产实践 2026/9/26 14:19:56

Redis内存优化:Hash为何比String省内存?底层原理与生产实践

先说个我在项目里反复验证过的结论:在 Redis 里存储"同一逻辑实体的一组属性"时,Hash 往往比拆成多个 String 省内存,而且省得很夸张。这不是面试题里的玄学,而是底层存储结构决定的。很多人写业务时习惯性set user:100…

阅读更多 →
六款降AI工具横评:知网AIGC检测通过率实测与避坑指南 2026/9/26 14:19:56

六款降AI工具横评:知网AIGC检测通过率实测与避坑指南

没有神器能一键让AI味变没。至少我在2025年底到2026年初这段时间,把市面上讨论度最高的六款降AI工具挨个试了一遍,用同一篇论文、同一个检测入口、同一套衡量标准跑完,结论还挺出乎意料:知网通过率最高的那款,和综合体…

阅读更多 →
Selenium反检测实战:编译级Chromedriver抹平浏览器自动化特征 2026/9/26 14:19:50

Selenium反检测实战:编译级Chromedriver抹平浏览器自动化特征

简介:面向Windows 10环境下的开发者与测试人员,这份Chromedriver驱动包已预编译并去除官方特征标识,配合内置的Portable Chrome浏览器,可直接用于自动化测试、爬虫采集及网页操控场景,无需额外编译,也无需复…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉