数据源及三方框架扩展

Kronos通过自定义创建继承KronosDataSourceWrapper接口的包装类,可以轻松与第三方框架结合使用。

Native 驱动

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

为现有框架实现wrapper

KronosDataSourceWrapper让Kronos使用项目中已有的数据库执行层。wrapper接收Kronos SQL task,并把它们委托给框架的query、update、batch和transaction API。

完整接口说明见📚数据源包装器。

Android SQLite 参考实现

独立的 📚Android SQLite 章节提供 Android/JVM SQLiteDatabase 配置和完整 wrapper 参考实现。

添加Spring JDBC和Kronos

Spring Boot项目通常由选定的Boot版本管理Spring依赖版本。JDBC Driver使用与数据库服务端、JDK匹配的最新稳定版。

maven

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.kotlinorm</groupId>
        <artifactId>kronos-core</artifactId>
        <version>0.4.0</version>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>${mysql-connector-j.version}</version>
    </dependency>
</dependencies>

gradle(kts)

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-jdbc")
    implementation("com.kotlinorm:kronos-core:0.4.0")
    implementation("com.mysql:mysql-connector-j:<latest-stable>")
}

从DataSource 读取元信息

wrapper从JDBC元信息中提供url、userName和dbType。Kronos使用dbType选择SQL渲染和表结构statement。

metadata

class SpringJdbcKronosWrapper(
    private val dataSource: DataSource
) : KronosDataSourceWrapper {
    override val url: String
    override val userName: String
    override val dbType: DBType

    private val namedJdbc = NamedParameterJdbcTemplate(dataSource)
    private val transactionManager = DataSourceTransactionManager(dataSource)

    init {
        dataSource.connection.use { conn ->
            url = conn.metaData.url
            userName = conn.metaData.userName ?: ""
            dbType = DBType.fromName(conn.metaData.databaseProductName)
        }
    }
}

委托查询

KAtomicQueryTask.sql使用命名参数,paramMap保存参数值,targetType保存完整的目标KType。自定义wrapper只需为Map、标量、KPojo和DTO实现一个列表方法和一个单行方法。

query mapping

import kotlin.reflect.jvm.jvmErasure

private fun rowMapper(targetType: KType): RowMapper<Any?> {
    val targetClass = targetType.jvmErasure

    return when {
        targetClass == Map::class -> ColumnMapRowMapper()
        targetClass in setOf(String::class, Int::class, Long::class, Boolean::class) ->
            SingleColumnRowMapper(targetClass.java)
        else -> DataClassRowMapper(targetClass.java)
    }
}

override fun toList(task: KAtomicQueryTask): List<Any?> =
    namedJdbc.query(task.sql, task.paramMap, rowMapper(task.targetType))

override fun first(task: KAtomicQueryTask): Any? =
    toList(task).firstOrNull()

query usage

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

委托写入

KAtomicActionTask携带一组参数。KronosAtomicBatchTask携带一条SQL和多组参数。

writes

override fun update(task: KAtomicActionTask): Int {
    return namedJdbc.update(task.sql, task.paramMap)
}

override fun batchUpdate(task: KronosAtomicBatchTask): IntArray {
    return namedJdbc.batchUpdate(task.sql, task.paramMapArr ?: emptyArray())
}

write usage

val affectedRows = wrapper.batchUpdate(
    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)
        )
    )
)

委托事务

transact(...)接收Kronos事务选项,并用TransactionScope执行block。传入Spring管理的JDBC connection后,可以使用TransactionScope savepoint。

transaction

override fun transact(
    isolation: TransactionIsolation?,
    timeout: Int?,
    block: TransactionScope.() -> Any?
): Any? {
    val definition = DefaultTransactionDefinition().apply {
        isolation?.let { isolationLevel = it.level }
        timeout?.let { this.timeout = it }
    }

    return TransactionTemplate(transactionManager, definition).execute {
        val connection = DataSourceUtils.getConnection(dataSource)
        try {
            TransactionScope(connection).block()
        } finally {
            DataSourceUtils.releaseConnection(connection, dataSource)
        }
    }
}

transaction usage

wrapper.transact(timeout = 30) {
    val point = savepoint("before_status_update")
    try {
        wrapper.update(
            KronosAtomicActionTask(
                sql = "UPDATE user SET status = :status WHERE id = :id",
                paramMap = mapOf("status" to "ACTIVE", "id" to 1)
            )
        )
        releaseSavepoint(point)
    } catch (e: Exception) {
        rollbackToSavepoint(point)
        throw e
    }
}

注册wrapper

应用启动时注册wrapper,或把它声明为Spring bean,并在初始化Kronos的位置赋值。

register

@Bean
fun kronosWrapper(dataSource: DataSource): KronosDataSourceWrapper {
    return SpringJdbcKronosWrapper(dataSource)
}

@Bean
fun kronosConfiguration(wrapper: KronosDataSourceWrapper): Any {
    Kronos.dataSource = { wrapper }
    return Any()
}

使用顺序参数框架

执行JDBC ?参数的框架可以使用task.parsed()和KronosAtomicBatchTask.parsedArr()。

single task

val task = KronosAtomicActionTask(
    sql = "UPDATE user SET status = :status WHERE id = :id",
    paramMap = mapOf("status" to "ACTIVE", "id" to 1)
)

val (jdbcSql, params) = task.parsed()

println(jdbcSql)
println(params.toList())

output

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

batch task

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

paramList.forEach { params ->
    preparedStatement.setObject(1, params[0])
    preparedStatement.setObject(2, params[1])
    preparedStatement.addBatch()
}

内置JDBC实现可以作为顺序参数执行的完整参考:KronosJdbcWrapper.kt。