数据库方言支持
内置数据库方言
Kronos 会通过 KronosDataSourceWrapper.dbType 选择 SQL 方言和表结构语句。
val wrapper = KronosJdbcWrapper(dataSource)
Kronos.dataSource = { wrapper }
println(wrapper.dbType)
println(wrapper.sqlDialect)内置完整方言对应以下 DBType:
| 数据库 | DBType |
|---|---|
| MySQL | DBType.Mysql |
| PostgreSQL | DBType.Postgres |
| SQLite | DBType.SQLite |
| H2 | DBType.H2 |
| SQL Server | DBType.Mssql |
| Oracle | DBType.Oracle |
| DM8(达梦) | DBType.DM8 |
连接数据库和 JDBC driver 示例请参考
📚连接到数据库。 表 DDL 工作流请参考📚表结构同步和📚基于查询创建表。
方言行为矩阵
本页作为方言行为矩阵入口。每一行都指向包含 Kotlin 入口和内置方言 SQL 形态的章节或页面。
| 行为 | 查阅位置 |
|---|---|
| 分页 | 见分页,查看 page(pageIndex, pageSize) 生成的 SQL。 |
Upsert 和 onConflict() | 见 Upsert 与 📚Upsert,查看按匹配字段 upsert、唯一约束冲突 upsert 和动态冲突赋值。 |
| 自增主键读取 | 见 Last Insert Id 与 📚Last Insert Id。 |
| H2 和 DM8 | 见下方独立章节,了解 H2 MERGE upsert、DM8 自增列和 JDBC generated key。 |
| DDL 和表结构同步 | 见表结构操作、📚表操作和📚表结构同步。 |
| 字段类型渲染 | 见 📚字段类型 中的 KColumnType 示例和方言输出。 |
| 标识符引用 | 见标识符引用,查看表名和字段名引用规则。 |
| 函数和窗口函数 | 见内置函数、📚函数和📚排序、分页和聚合。 |
标识符引用
同一段查询会按当前方言引用表名和字段名。
kotlin
val users = User()
.select { [it.id, it.name] }
.toList()Mysql
SELECT `id`, `name`
FROM `user`PostgreSQL
SELECT "id", "name"
FROM "user"SQLite
SELECT "id", "name"
FROM "user"SQLServer
SELECT [id], [name]
FROM [user]Oracle
SELECT "ID", "NAME"
FROM "USER"分页
page(pageIndex, pageSize) 的页码从 1 开始。需要总数元数据时,在 page(...) 后调用 withTotal(),得到命名的 PageResult。Kronos 会把相同 DSL 渲染为当前数据库的分页语法。
kotlin
val page = User()
.select { [it.id, it.name] }
.orderBy { it.id.asc() }
.page(2, 20)
.withTotal()
.toList()
val users = page.recordsMysql
SELECT `id`, `name`
FROM `user`
ORDER BY `id` ASC
LIMIT 20 OFFSET 20PostgreSQL
SELECT "id", "name"
FROM "user"
ORDER BY "id" ASC
LIMIT 20 OFFSET 20SQLite
SELECT "id", "name"
FROM "user"
ORDER BY "id" ASC
LIMIT 20 OFFSET 20SQLServer
SELECT [id], [name]
FROM [user]
ORDER BY [id] ASC
OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLYOracle
SELECT "ID", "NAME"
FROM "USER"
ORDER BY "ID" ASC
OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLYUpsert
onConflict() 按数据库唯一约束冲突处理更新,并按当前方言渲染对应 SQL。省略 on { ... } 时,Kronos 会根据 KPojo 唯一性元数据推导冲突目标,包括主键值和已声明的唯一索引。
kotlin
User(id = 1, name = "Ada")
.upsert { it.name }
.onConflict()
.execute()Mysql
INSERT INTO `user` (`id`, `name`) VALUES (:id, :name)
ON DUPLICATE KEY UPDATE `name` = :namePostgreSQL
INSERT INTO "user" ("id", "name") VALUES (:id, :name)
ON CONFLICT ("id") DO UPDATE SET "name" = :nameSQLite
INSERT INTO "user" ("id", "name") VALUES (:id, :name)
ON CONFLICT ("id") DO UPDATE SET "name" = :nameSQLServer
MERGE INTO [user] AS [t1]
USING (SELECT :id AS [id], :name AS [name]) AS [t2]
ON ([t1].[id] = [t2].[id])
WHEN MATCHED THEN UPDATE SET [t1].[name] = :name
WHEN NOT MATCHED THEN INSERT ([id], [name]) VALUES (:id, :name)Oracle
MERGE INTO "USER" "T1"
USING (SELECT :id AS "ID", :name AS "NAME") "T2"
ON ("T1"."ID" = "T2"."ID")
WHEN MATCHED THEN UPDATE SET "T1"."NAME" = :name
WHEN NOT MATCHED THEN INSERT ("ID", "NAME") VALUES (:id, :name)Last Insert Id
自增主键 insert 调用 .withId() 后可以读取 lastInsertId。内置 KronosJdbcWrapper 会在 insert 执行时读取 JDBC generated keys;使用后续查询读取生成 ID 的 wrapper 会使用下面的方言 SQL。
kotlin
val id = User(name = "Kronos")
.insert()
.withId()
.execute()
.lastInsertIdMysql
SELECT LAST_INSERT_ID()PostgreSQL
SELECT LASTVAL()SQLite
SELECT last_insert_rowid()SQLServer
SELECT SCOPE_IDENTITY()Oracle
SELECT MAX("ID") FROM "USER"表结构操作
table.createTable(...)、table.syncTable(...)、table.dropTable(...) 和 table.truncateTable(...) 会使用当前数据库的 DatabaseStatements。
kotlin
val table = Kronos.dataSource.table
table.createTable(Account())
table.syncTable(Account())
table.truncateTable(Account())
table.dropTable(Account())Mysql
CREATE TABLE `account` (`id` SIGNED NOT NULL, `name` CHAR(64), `created_at` DATETIME)PostgreSQL
CREATE TABLE "account" ("id" INTEGER NOT NULL, "name" VARCHAR(64), "created_at" TIMESTAMP)SQLite
CREATE TABLE "account" ("id" INTEGER NOT NULL, "name" VARCHAR(64), "created_at" TIMESTAMP)SQLServer
CREATE TABLE [account] ([name] NVARCHAR(32), [created_at] DATETIME2)Oracle
CREATE TABLE "ACCOUNT" ("NAME" VARCHAR(4000), "SCORE" INT)表结构同步会先读取当前表结构,再生成需要执行的 DDL。
kotlin
val changed = Kronos.dataSource.table.syncTable(User())result
changed == false // Kronos 创建目标表
changed == true // Kronos 按差异同步已有表内置函数
函数 DSL 保持相同的 Kotlin 调用方式,由当前方言选择对应 SQL 形式。函数可用范围和参数语义见📚函数。
kotlin
val rows = User()
.select { f.rand().alias("rand") }
.toList()Mysql
SELECT RAND() AS rand
FROM `tb_user`PostgreSQL
SELECT RANDOM() AS rand
FROM "tb_user"SQLite
SELECT (RANDOM() / 9223372036854775808.0 + 0.5) AS rand
FROM "tb_user"H2
SELECT RAND() AS rand
FROM "tb_user"SQLServer
SELECT RAND() AS rand
FROM [tb_user]Oracle
SELECT DBMS_RANDOM.VALUE AS RAND
FROM "TB_USER"DM8
SELECT DBMS_RANDOM.VALUE AS RAND
FROM "TB_USER"H2
H2 使用双引号引用标识符,并通过 H2 MERGE 语法处理唯一约束冲突的 upsert。内存库和服务端连接配置见📚连接到数据库。
kotlin
User(id = 1, name = "Ada")
.upsert { it.name }
.onConflict()
.execute()H2
MERGE INTO "user" AS "t1"
USING (VALUES (:id, :name)) AS "t2" ("id", "name")
ON ("t1"."id" = "t2"."id")
WHEN MATCHED THEN UPDATE SET "name" = "t2"."name"
WHEN NOT MATCHED THEN INSERT ("id", "name") VALUES ("t2"."id", "t2"."name")H2 自增字段使用 H2 的 identity 列定义。带 .withId() 的插入任务会返回 JDBC generated key。
DM8(达梦)
DM8 的查询使用 Oracle 兼容语法,表结构使用 DM8 原生自增列。JDBC Driver 配置见📚连接到数据库。
kotlin
@PrimaryKey(identity = true)
var id: Int? = nullKronos 会将该字段渲染为 DM8 DDL 中的 INT IDENTITY(1,1)。带 .withId() 的插入任务会返回 JDBC generated key。
数据库枚举与扩展
DBType 还包含 DB2、Sybase、OceanBase、GaussDB 等枚举值,用于连接识别和方言扩展入口。
val dbType = DBType.fromName(connection.metaData.databaseProductName)添加新的数据库方言请参考📚创建数据库方言。
自定义数据库连接请参考📚概念 - 数据源包装器。