Code Generator

This chapter introduces the Kronos code generator, which automatically generates entity classes from database tables.

Code Generator

Kronos code generator reads database metadata and writes Kotlin KPojo entity classes. Use it when the database schema is the source of truth and the project needs entity classes that match existing tables.

The generator uses a TOML config file and a Kotlin Script template. The config selects tables, data source, naming strategy, and output directory. The template controls the generated Kotlin file content.

For the Database First workflow, see Database First. For setup and runtime troubleshooting, see 📚Troubleshooting.

Dependencies Add script dependencies

Add kronos-codegen, kronos-core, and kronos-jdbc-wrapper to the .main.kts script. Add the JDBC driver and connection pool that match your database and JDK.

example.main.kts

#!/usr/bin/env kotlin

@file:Repository("https://repo1.maven.org/maven2")
@file:DependsOn("com.kotlinorm:kronos-codegen:0.4.0")
@file:DependsOn("com.kotlinorm:kronos-core:0.4.0")
@file:DependsOn("com.kotlinorm:kronos-jdbc-wrapper:0.4.0")
@file:DependsOn("org.apache.commons:commons-dbcp2:<latest-stable>")
@file:DependsOn("com.mysql:mysql-connector-j:<latest-stable>")

Replace <latest-stable> with the current stable version from the connection pool or JDBC driver project.

Configuration Select tables

Create config.toml. Each [[table]] item becomes one Kotlin file.

config.toml

[[table]]
name = "tb_user"
className = "User"

[[table]]
name = "tb_order"
className = "Order"

With the output config below, the generator writes one file per table.

generated files

src/main/kotlin/com/example/entity/User.kt
src/main/kotlin/com/example/entity/Order.kt

Set className when the Kotlin class name should be controlled explicitly.

class name

[[table]]
name = "sys_account"
className = "Account"

Configure output

targetDir selects the generated file directory. packageName selects the package declaration written by the template.

config.toml

[output]
targetDir = "src/main/kotlin/com/example/entity"
packageName = "com.example.entity"
tableCommentLineWords = 80

The template can read these values directly.

template

template {
    +"package $packageName"
    +""
    +formatedComment
}

The generated file starts with the configured package and the formatted table comment.

User.kt

package com.example.entity

// User account table

Configure a data source

dataSource creates the JDBC connection used to read table comments, columns, and indexes. This example uses Apache DBCP2 and the built-in KronosJdbcWrapper.

config.toml

[dataSource]
wrapperClassName = "com.kotlinorm.wrappers.KronosJdbcWrapper"
dataSourceClassName = "org.apache.commons.dbcp2.BasicDataSource"
url = "jdbc:mysql://localhost:3306/kronos_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false"
username = "root"
password = "your_password"
driverClassName = "com.mysql.cj.jdbc.Driver"
initialSize = 5
maxTotal = 10

Use the property names supported by the selected DataSource class.

Druid

[dataSource]
wrapperClassName = "com.kotlinorm.wrappers.KronosJdbcWrapper"
dataSourceClassName = "com.alibaba.druid.pool.DruidDataSource"
url = "jdbc:mysql://localhost:3306/kronos_demo"
username = "root"
password = "your_password"
driverClassName = "com.mysql.cj.jdbc.Driver"
initialSize = 5
maxActive = 10

Add the matching pool dependency to the script when you switch the pool implementation.

Druid dependency

@file:DependsOn("com.alibaba:druid:<latest-stable>")

Configure naming

lineHumpNamingStrategy maps underscore names to camel-case Kotlin property names.

config.toml

[strategy]
tableNamingStrategy = "lineHumpNamingStrategy"
fieldNamingStrategy = "lineHumpNamingStrategy"

With this strategy, a database column named create_time is generated as createTime.

User.kt

var createTime: java.time.LocalDateTime? = null

Use noneNamingStrategy when the generated name should keep the database metadata name.

keep names

[strategy]
tableNamingStrategy = "noneNamingStrategy"
fieldNamingStrategy = "noneNamingStrategy"

User.kt

var create_time: java.time.LocalDateTime? = null

Generate special field annotations

Add strategy fields for primary key, create time, update time, logic delete, and optimistic lock columns.

config.toml

[strategy]
primaryKeyStrategy = "id"
createTimeStrategy = "create_time"
updateTimeStrategy = "update_time"
logicDeleteStrategy = "deleted"
optimisticLockStrategy = "version"

When the database table contains matching columns, field.annotations() writes the Kronos annotations. The logical-delete column must define DEFAULT 0 (DEFAULT false on PostgreSQL) for Codegen to emit the @Default annotation shown below.

User.kt

import com.kotlinorm.annotations.CreateTime
import com.kotlinorm.annotations.Default
import com.kotlinorm.annotations.LogicDelete
import com.kotlinorm.annotations.PrimaryKey
import com.kotlinorm.annotations.UpdateTime
import com.kotlinorm.annotations.Version

data class User(
    @PrimaryKey(identity = true)
    var id: Int? = null,
    @CreateTime
    var createTime: java.time.LocalDateTime? = null,
    @UpdateTime
    var updateTime: java.time.LocalDateTime? = null,
    @LogicDelete
    @Default("0") // @Default("false") for Postgres
    var deleted: Boolean? = null,
    @Version
    var version: Int? = null
) : KPojo

For annotation behavior in ORM operations, see 📚Annotation Config.

Generate column metadata annotations

The template can turn database metadata into field annotations. A non-null database column is generated with @NonNull.

template

fields.forEach { field ->
    field.annotations().forEach { annotation ->
        +"""${indent(4)}$annotation"""
    }
    +"""${indent(4)}var ${field.name}: ${field.kotlinType}? = null,"""
}

For a required name column, the generated property includes @NonNull.

User.kt

@NonNull
var name: String? = null

A decimal column with precision metadata is generated with @ColumnType.

User.kt

@ColumnType(type = KColumnType.DECIMAL, length = 10, scale = 2)
var balance: java.math.BigDecimal? = null

Generate table indexes

Use indexes.toAnnotations() to write index annotations collected from the database metadata.

template

+indexes.toAnnotations()
+"""@Table("$tableName")"""

The generated entity includes @TableIndex before the class declaration.

User.kt

@TableIndex(name = "idx_user_email", columns = ["email"], type = "UNIQUE")
@Table("tb_user")
data class User(
    var email: String? = null
) : KPojo

Create the template

Call init("config.toml") before template { ... }. The template block runs once for each configured table.

example.main.kts

import com.kotlinorm.codegen.KronosConfig.Companion.write
import com.kotlinorm.codegen.TemplateConfig.Companion.template
import com.kotlinorm.codegen.init
import com.kotlinorm.codegen.kotlinType

init("config.toml")

template {
    +"package $packageName"
    +""
    +imports.joinToString("
") { "import $it" }
    +""
    +formatedComment
    +indexes.toAnnotations()
    +"""@Table("$tableName")"""
    +"""data class $className("""
    +fields.mapIndexed { index, field ->
        val annotations = field.annotations().joinToString("
") { "${indent(4)}$it" }
        val annotationBlock = if (annotations.isEmpty()) "" else "$annotations
"
        val comma = if (index == fields.lastIndex) "" else ","
        """$annotationBlock${indent(4)}var ${field.name}: ${field.kotlinType}? = null$comma"""
    }.joinToString("
")
    +") : KPojo"
}.write()

The generated file follows the structure assembled by the template.

User.kt

package com.example.entity

import com.kotlinorm.annotations.Table
import com.kotlinorm.interfaces.KPojo

@Table("tb_user")
data class User(
    var id: Int? = null,
    var name: String? = null
) : KPojo

Use config inheritance

Put shared output and data source settings in a base TOML file.

base.toml

[output]
targetDir = "src/main/kotlin/com/example/entity"
packageName = "com.example.entity"

[dataSource]
wrapperClassName = "com.kotlinorm.wrappers.KronosJdbcWrapper"
dataSourceClassName = "org.apache.commons.dbcp2.BasicDataSource"
url = "jdbc:mysql://localhost:3306/kronos_demo"
username = "root"
password = "your_password"
driverClassName = "com.mysql.cj.jdbc.Driver"

The project config can extend the base file and add its own table list.

config.toml

extend = "base.toml"

[[table]]
name = "tb_user"
className = "User"

Run the script with the project config.

example.main.kts

init("config.toml")

Run the generator

Kotlin script files should use the xxx.main.kts name format.

shell

kotlinc -script example.main.kts

After a successful run, the generator logs each written file.

output

File generated successfully: src/main/kotlin/com/example/entity/User.kt

If the config misses a required section, the script exits with a clear message.

missing table

IllegalArgumentException: Table configuration is required in config: config.toml