数据源包装器

KronosDataSourceWrapper连接Kronos SQL task和数据库执行层。内置JDBC实现是KronosJdbcWrapper,自定义wrapper可以把执行委托给Spring JDBC、JDBI、MyBatis或项目中的数据访问层。

Native 驱动

为平台驱动实现 KronosExecutionDataSource,再通过 KronosExecutionWrapper 执行共享 ORM 查询。完整连接示例见 📚Native SQLite。

配置默认数据源

Kronos.connect(...)配置Kronos.dataSource,并返回KronosJdbcWrapper。

kotlin

import com.kotlinorm.Kronos
import com.kotlinorm.connect

Kronos.connect(
    url = "jdbc:postgresql://localhost:5432/kronos",
    userName = "postgres",
    password = "******",
    driverClassName = "org.postgresql.Driver"
)

方言需要显式指定时传入databaseType,JDBC wrapper设置放在配置block中。

创建多个数据源的wrapper

读写分离或按租户选择数据源时,可以用DriverManagerDataSource创建独立wrapper。

kotlin

import com.kotlinorm.enums.DBType
import com.kotlinorm.wrappers.DriverManagerDataSource
import com.kotlinorm.wrappers.KronosJdbcWrapper

val reportingWrapper = KronosJdbcWrapper(
    dataSource = DriverManagerDataSource(
        url = "jdbc:postgresql://localhost:5432/kronos",
        userName = "postgres",
        password = "******",
        driverClassName = "org.postgresql.Driver"
    ),
    databaseType = DBType.Postgres
)

事务需要固定使用该数据源时,调用Kronos.transact(reportingWrapper)。应用服务使用连接池时,添加连接池和JDBC Driver后,将连接池的DataSource传给KronosJdbcWrapper。

Android SQLite

Android/JVM SQLiteDatabase 配置和完整 wrapper 参考实现见📚Android SQLite。

成员属性

名称说明类型
url数据库连接URLString
userName数据库用户名String
dbTypeSQL渲染和DDL statement使用的数据库类型DBType
sqlDialect由dbType解析出的SQL方言SqlDialect

创建wrapper后可以读取这些属性。

kotlin

val wrapper = KronosJdbcWrapper(dataSource)

println(wrapper.url)
println(wrapper.userName)
println(wrapper.dbType)
println(wrapper.sqlDialect.family)

output

jdbc:postgresql://localhost:5432/kronos
postgres
Postgres
PostgreSql

配置内置JDBC wrapper

KronosJdbcWrapper接收DataSource,包括DriverManagerDataSource或连接池数据源、可选的DBType和配置block。需要显式指定方言时,可以传入databaseType。

database type

import com.kotlinorm.enums.DBType
import com.kotlinorm.wrappers.KronosJdbcWrapper

val wrapper = KronosJdbcWrapper(
    dataSource = dataSource,
    databaseType = DBType.Mysql
)

JDBC statement设置和SQL warning处理可以放在配置block里。

statement

import com.kotlinorm.wrappers.KronosDuplicateKeyException
import com.kotlinorm.wrappers.KronosJdbcWrapper
import com.kotlinorm.wrappers.KronosSqlWarningPolicy
import com.kotlinorm.wrappers.SqlStateSQLExceptionTranslator

val wrapper = KronosJdbcWrapper(dataSource) {
    statement.fetchSize = 1000
    statement.maxRows = 5000
    statement.queryTimeoutSeconds = 30
    warningPolicy = KronosSqlWarningPolicy.THROW
    exceptionTranslator = { sql, params, exception ->
        if (exception.errorCode == 1062) {
            KronosDuplicateKeyException("Duplicate key", sql, params, exception)
        } else {
            SqlStateSQLExceptionTranslator().translate(sql, params, exception)
        }
    }
}

内置wrapper也提供自定义参数绑定和结果映射注册入口。

mapping

import com.kotlinorm.wrappers.KronosArgument
import com.kotlinorm.wrappers.KronosArgumentFactory
import com.kotlinorm.wrappers.KronosJdbcWrapper
import com.kotlinorm.wrappers.KronosPhysicalReadResult
import com.kotlinorm.wrappers.KronosPhysicalValueReader
import java.sql.Types
import java.util.UUID

val wrapper = KronosJdbcWrapper(dataSource) {
    arguments.register(KronosArgumentFactory { value, _ ->
        if (value is UUID) {
            KronosArgument { position, statement, _ ->
                statement.setObject(position, value, Types.OTHER)
            }
        } else {
            null
        }
    })

    columnMappers.register(KronosPhysicalValueReader { resultSet, position, _ ->
        val jdbcTypeName = resultSet.metaData.getColumnTypeName(position)
        if (jdbcTypeName.equals("jsonb", ignoreCase = true)) {
            KronosPhysicalReadResult.Handled(resultSet.getString(position))
        } else {
            KronosPhysicalReadResult.NotHandled
        }
    })
}

物理 reader 会在ResultSet.getObject(position)之前检查 JDBC metadata 或 driver 行为。已经处理且值为 SQL NULL 时返回Handled(null);需要继续默认 JDBC 读取时返回NotHandled。读取到的字符串需要转换为 UUID 等领域值时,注册一份自定义值映射。

使用toList(task) 查询列表

toList(task)执行select task,并根据task.targetType映射每一行。KType会保留泛型参数和可空性,因此wrapper不再需要额外接收KClass、isKPojo或父类型参数。

signature

fun toList(task: KAtomicQueryTask): List<Any?>

kotlin

val users = wrapper.toList(
    KronosAtomicQueryTask(
        sql = "SELECT id, name FROM user WHERE age > :age",
        paramMap = mapOf("age" to 18),
        targetType = typeOf<User>()
    )
)

内置 JDBC wrapper 会根据目标 KType 识别标量、typed row Map 和 KPojo。每行 JDBC 结果都会物化为 LinkedHashMap,因此只支持直接声明的 Map 和 MutableMap(顶层目标可空也支持)。固定实现、调整泛型顺序或自定义 Map 子类型都会被拒绝,因为 Kronos 无法安全返回声明的容器。Map key 只支持 String、String? 或 *;Any、Any? 和 * value 保持 raw,具体 value 参数则使用完整 KType 转换。

使用first(task) 查询单个值

first(task)使用相同的目标类型,空结果返回null。上层first<T>()会根据请求类型是否可空决定空结果是否抛出异常。

signature

fun first(task: KAtomicQueryTask): Any?

kotlin

val user = wrapper.first(
    KronosAtomicQueryTask(
        sql = "SELECT id, name FROM user WHERE id = :id",
        paramMap = mapOf("id" to 1),
        targetType = typeOf<User?>()
    )
)

使用update(task) 执行写入

update(task)执行insert、update、delete和DDL task,并返回影响行数。

signature

fun update(task: KAtomicActionTask): Int

kotlin

val affectedRows = wrapper.update(
    KronosAtomicActionTask(
        sql = "UPDATE user SET name = :name WHERE id = :id",
        paramMap = mapOf("name" to "Ada", "id" to 1)
    )
)

result

1

使用batchUpdate(task) 执行批量写入

batchUpdate(task)用多组参数执行同一条SQL,并返回每个批次项的JDBC update count。

signature

fun batchUpdate(task: KronosAtomicBatchTask): IntArray

kotlin

val affectedRows = wrapper.batchUpdate(
    KronosAtomicBatchTask(
        sql = "UPDATE user SET name = :name WHERE id = :id",
        paramMapArr = arrayOf(
            mapOf("name" to "Ada", "id" to 1),
            mapOf("name" to "Linus", "id" to 2)
        )
    )
)

result

[1, 1]

使用transact 执行事务

transact(...)用TransactionScope作为block receiver。JDBC wrapper可以在block中使用savepoint、rollbackToSavepoint和releaseSavepoint。

signature

fun transact(
    isolation: TransactionIsolation? = null,
    timeout: Int? = null,
    block: TransactionScope.() -> Any?
): Any?

kotlin

val result = wrapper.transact(
    isolation = TransactionIsolation.READ_COMMITTED,
    timeout = 30
) {
    val point = savepoint("before_user_update")

    try {
        wrapper.update(
            KronosAtomicActionTask(
                sql = "UPDATE user SET name = :name WHERE id = :id",
                paramMap = mapOf("name" to "Ada", "id" to 1)
            )
        )
        releaseSavepoint(point)
    } catch (e: Exception) {
        rollbackToSavepoint(point)
        throw e
    }
}

转换命名参数

task.parsed()把:name参数转换为JDBC ?占位符。KronosAtomicBatchTask.parsedArr()为每个批次项返回同样的结构。

query task

val task = KronosAtomicQueryTask(
    sql = "SELECT id, name FROM user WHERE id = :id AND status = :status",
    paramMap = mapOf("id" to 1, "status" to "ACTIVE"),
    targetType = typeOf<Map<String, Any?>>()
)

val parsed = task.parsed()

println(parsed.jdbcSql)
println(parsed.jdbcParamList.toList())

output

SELECT id, name FROM user WHERE id = ? AND status = ?
[1, ACTIVE]

batch task

val batchTask = KronosAtomicBatchTask(
    sql = "UPDATE user SET status = :status WHERE id = :id",
    paramMapArr = arrayOf(
        mapOf("status" to "ACTIVE", "id" to 1),
        mapOf("status" to "LOCKED", "id" to 2)
    )
)

val (jdbcSql, batchParams) = batchTask.parsedArr()

println(jdbcSql)
println(batchParams)

batch output

UPDATE user SET status = ? WHERE id = ?
[[ACTIVE, 1], [LOCKED, 2]]

Spring JDBC wrapper示例见📚数据源及三方框架扩展。