编译器插件
配置 Kronos 编译器插件,并读取用户可见的 DSL 诊断。
设置 Kotlin 项目基线
Kronos 编译期支持由 Kotlin 构建加载。项目使用 JDK 8+、Kotlin 2.4.21+、Maven 3.9+,或 Kotlin 2.4.21 支持的 Gradle 版本。
gradle(kts)
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm") version "2.4.21"
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
}
}maven
<properties>
<kotlin.version>2.4.21</kotlin.version>
<maven.compiler.release>8</maven.compiler.release>
</properties>先让基础 Kotlin 构建通过,再添加 Kronos 数据源和 ORM 代码。
output
> Task :compileKotlin
BUILD SUCCESSFUL启用kronos-gradle-plugin
在 JVM Kotlin 项目中添加 Kronos Gradle 插件和 kronos-core。Gradle 插件会把 Kronos 编译期支持接入 compileKotlin。
build.gradle.kts
plugins {
kotlin("jvm") version "2.4.21"
id("com.kotlinorm.kronos-gradle-plugin") version "0.4.0"
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xcollection-literals", "-Xcontext-sensitive-resolution")
}
}
dependencies {
implementation("com.kotlinorm:kronos-core:0.4.0")
}运行 Kotlin 编译任务,并检查插件加载输出。
shell
./gradlew compileKotlinoutput
Loaded Gradle plugin com.kotlinorm.compiler.plugin.KronosGradlePlugin version 0.4.0
Loaded Compiler plugin com.kotlinorm.kronos-compiler-plugin version 0.4.0
BUILD SUCCESSFUL启用kronos-maven-plugin
在 Maven 中,将 kronos-core 作为应用依赖,并在 kotlin-maven-plugin 中注册 kronos-maven-plugin。
pom.xml
<project>
<properties>
<kotlin.version>2.4.21</kotlin.version>
<kronos.version>0.4.0</kronos.version>
</properties>
<dependencies>
<dependency>
<groupId>com.kotlinorm</groupId>
<artifactId>kronos-core</artifactId>
<version>${kronos.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<extensions>true</extensions>
<configuration>
<args>
<arg>-Xcollection-literals</arg>
<arg>-Xcontext-sensitive-resolution</arg>
</args>
<compilerPlugins>
<plugin>kronos-maven-plugin</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>com.kotlinorm</groupId>
<artifactId>kronos-maven-plugin</artifactId>
<version>${kronos.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>使用 Maven 编译,并检查 Kronos 插件加载输出。
shell
mvn compileoutput
Loaded Maven plugin com.kotlinorm.compiler.plugin.KronosMavenPlugin
[INFO] BUILD SUCCESS编译每个 Kronos source set
每个声明 KPojo 类或使用 Kronos DSL 的 JVM source set 都需要启用 Kronos 构建插件。只消费已编译实体的模块可以正常依赖上游模块;但只要本模块声明实体或编写查询 DSL,就需要在本模块编译时加载 Kronos 插件。
module layout
// :domain 声明 KPojo,需要应用 Kronos Gradle 或 Maven 插件。
@Table("tb_user")
data class User(
@PrimaryKey
var id: Int? = null,
var name: String? = null
) : KPojo
// :service 基于 User 编写 DSL,也需要经过 Kronos 编译期支持。
val names = User()
.select { [it.id, it.name] }
.where { it.name like "A%" }
.toList()最快的检查方式是编译拥有这些源码的模块。
shell
./gradlew :service:compileKotlinoutput
[Kronos] Kronos compiler plugin K2 initialized
BUILD SUCCESSFUL检查生成的KPojo 成员
构建插件生效后,KPojo 可以在运行时检查中暴露生成的元数据和 Map 转换方法。
User.kt
import com.kotlinorm.annotations.PrimaryKey
import com.kotlinorm.annotations.Table
import com.kotlinorm.interfaces.KPojo
@Table("tb_user")
data class User(
@PrimaryKey
var id: Int? = null,
var name: String? = null
) : KPojo
fun main() {
val user = User(7, "Ada")
println(user.__tableName)
println(user.toDataMap())
println(user.__columns.map { it.name })
}输出应包含表元数据和 Kronos 收集到的属性值。
output
tb_user
{id=7, name=Ada}
[id, name]如果出现
__tableName must be overridden by the compiler plugin,表示该 source set 没有经过 Kronos 编译期支持处理。
检查 DSL SQL 输出
使用一个小的 select 查询确认 Kotlin 属性访问和条件表达式已转换为 Kronos SQL。
kotlin
import com.kotlinorm.orm.select.select
val (sql, params) = User()
.select { [it.id, it.name] }
.where { it.id == 7 }
.build()
println(sql)
println(params)使用 MySQL 数据源时,检查输出会包含选择字段和命名参数。
output
SELECT `id`, `name` FROM `tb_user` WHERE `tb_user`.`id` = :id
{id=7}条件操作符和 query-by-example 行为请参考 📚Where、Having 和 On 条件。
使用 kronos-syntax 的 DSL 规则
Kronos 的查询、变更和 DDL API 都会在执行前生成可检查的 SQL 任务。需要先查看 SQL 和参数时,调用 build(),不需要连接数据库。
projection and condition
val task = User()
.select {
[
it.id,
it.name.alias("username"),
f.length(it.name).alias("nameLength")
]
}
.where { (it.id in listOf(1, 2, 3)) && it.name like "A%" }
.build()
println(task.sql)
println(task.paramMap)output
SELECT `id`, `name` AS `username`, LENGTH(`name`) AS `nameLength`
FROM `tb_user`
WHERE `tb_user`.`id` IN (:idList) AND `tb_user`.`name` LIKE :name
{idList=[1, 2, 3], name=A%}同一套规则适用于 where、having 和 on:Kotlin 比较会变成 SQL 谓词,&& 和 || 保留布尔组合,in 可以接集合或可查询子查询,非直接字段投影需要 .alias("name")。
derived projection
val nameLengths = User()
.select { [it.id, f.length(it.name).alias("nameLength")] }
val rows = nameLengths
.select { [it.id, it.nameLength] }
.where { it.nameLength > 8 }
.toList()读取 Kronos 诊断
Kronos 诊断会出现在 IDE 和构建输出中。KRONOS_ 代码用于定位 DSL 规则,诊断消息会说明修复方式。
invalid select
import com.kotlinorm.orm.select.select
User()
.select { [it.id, f.length(it.name)] }这个选择项是函数表达式,因此诊断会要求显式设置别名。
output
User.kt:6:23: error: Non-field select item must declare .alias("name")
KRONOS_SELECT_ITEM_REQUIRES_ALIAS添加别名后重新编译。
fixed select
User()
.select { [it.id, f.length(it.name).alias("nameLength")] }Kronos 发出的编译插件错误也会以 [Kronos] 开头,并可能带有 Fix: 行。
kronos output
User.kt:12:13: error: [Kronos] Unsupported field
Fix: Use a property reference修复常见诊断
通过诊断代码定位需要调整的 DSL 形态。
| 诊断 | 检查点 | 修复方式 |
|---|---|---|
__tableName must be overridden by the compiler plugin | 当前 source set 没有经过 Kronos 编译期支持 | 为该模块启用 Gradle 或 Maven 插件后重新编译 |
KRONOS_GENERIC_KPOJO_NOT_SUPPORTED | KPojo 类声明了类级泛型参数 | 移除 KPojo 类的泛型参数,并使用具体的属性类型 |
KRONOS_SELECT_ITEM_REQUIRES_ALIAS | select { ... } 中包含函数、聚合、标量子查询、窗口函数或原生 SQL | 添加 .alias("resultName") |
KRONOS_DUPLICATE_PROJECTION_FIELD | 两个投影项生成了同名结果属性 | 删除重复字段,或给其中一个投影项设置不同 alias |
OPT_IN_USAGE_ERROR | selected alias 替换了仍存在的同名 source 字段 | 修改 alias、先移除 source 字段,或使用 @OptIn(UnsafeProjectionOverride::class) 明确确认 |
KRONOS_SCALAR_SUBQUERY_REQUIRES_LIMIT | 标量子查询可能返回多行 | 作为值使用前添加 .limit(1) |
KRONOS_SCALAR_SUBQUERY_REQUIRES_SINGLE_COLUMN | 标量子查询选择了多列 | 只选择一个字段,或拆成多个表达式 |
KRONOS_PREDICATE_SUBQUERY_COLUMN_COUNT_MISMATCH | field in query、ANY、SOME、ALL 或 row-value IN 左右列数不一致 | 让左侧表达式和右侧查询返回相同列数 |
KRONOS_ROW_VALUE_TUPLE_REQUIRES_MULTIPLE_FIELDS | 使用了 [it.id] in query 这样的单字段 tuple | 改写为 it.id in query |
KRONOS_INSERT_SELECT_VALUE_COUNT_MISMATCH | insert<Target> { ... } 值数量与目标字段数量不一致 | 选择与目标可插入列数量一致的值 |
KRONOS_INSERT_SELECT_VALUE_TYPE_MISMATCH | insert-select 值类型和目标字段类型不匹配 | 调整顺序,或把标量表达式 cast 成预期 Kotlin 类型 |
显式 projection alias 通常不能替换仍存在的同名 source 字段。如果这种替换是有意的,请使用 Kotlin 标准 opt-in 机制。标记为 com.kotlinorm.annotations.UnsafeProjectionOverride,严重级别为 error;提示会说明 selected 值和 Kotlin 类型可能发生变化。
import com.kotlinorm.annotations.UnsafeProjectionOverride
@OptIn(UnsafeProjectionOverride::class)
val query = User().select { [it.id, f.length(it.name).alias("name")] }先移除字段再恢复同名 alias 时,不需要 opt-in:
val query = User().select { [it - it.name, f.length(it.name).alias("name")] }构建配置从 📚Quick Start 开始。数据库执行和 SQL 日志请参考 📚连接到数据库 和 📚日志。IDE 检查行为请参考 📚KEFS 编辑器支持。