研究Ktor时发现jb的exposed框架,挺有意思的,正好compose项目需要存取数据库
踩坑
二进制运行报错
运行gradle:run时可正常连接数据库,然后运行gradle:createDistributable生成二进制文件,打开报错NoClassDefException: java/sql/Connection
经gpt提醒,是生成二进制时对jre进行了裁切,把java.sql去掉了,要到build.gradle.kts里面加上对应模块
compose.desktop {
application {
mainClass = "xxx.xxx.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "xxx.xxx"
packageVersion = "1.0.0"
// 添加以下代码
// ✅ 指定要打包的 JDK 模块
modules.addAll(
arrayOf(
"java.sql", // JDBC API
"java.logging", // sqlite-jdbc 里有用到日志
"java.naming" // 部分 JDBC 驱动会用到 JNDI
)
)
}
}
}
最后生成的exe可以就读写数据库了
代码混淆
gradle:createReleaseDistributable生成发布版本二进制文件会有一步代码混淆,有提示一堆警告要求消除(
暂时先空着,等解决了再补上( ̄y▽, ̄)╭
示例
后面是在compose中添加并使用exposed,以sqlite为例
添加依赖
在gradle/libs.version.toml中添加
[version]
# ......
exposed = "1.0.0-beta-5"
sqlite = "3.50.3.0"
[libraries]
# ......
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
sqlite-jdbc = { module="org.xerial:sqlite-jdbc", version.ref = "sqlite" }
然后到build.gradle.kts中添加
jvmMain.dependencies {
// ......
implementation(libs.exposed.core)
implementation(libs.exposed.jdbc)
implementation(libs.sqlite.jdbc)
}
然后同步gradle项目
数据表对象
还是很喜欢kotlin这种在代码里面配置一切的风格(
以最常见的用户为例
字段名称 | 说明 | 字段类型 |
---|---|---|
name | 用户名,主键 | text |
password | 密码 | text |
@Serializable
data class DbUser(
val name: String,
val password: String,
)
建表直接用代理类和对象
class UserService(database: Database) {
object Users: Table() {
val name = text("name")
val password = text("password")
override val primaryKey = PrimaryKey(name)
}
init {
transaction(database) {
if(!Users.exists()) {
// 如果表不存在就建表
SchemaUtils.create(Users)
}
}
}
private suspend fun <T> dbQuery(block: suspend () -> T) : T =
newSuspendedTransaction(Dispatchers.IO) { block() }
// 查询全部
suspend fun read(): List<DbUser> = dbQuery {
Users.selectAll().map {
DbUser(
it[Users.name],
it[Users.password]
)
}
}
// 插入
suspend fun create(user: Users) = dbQuery {
Users.insert {
it[name] = user.name
it[password] = user.password
}
}
}
使用
来自gpt老师的建议
✅ 我的建议
个人工具项目,只要你不打算做复杂依赖切换,用单例对象最合适,维护成本最低。
如果你觉得以后 UI 测试 / 依赖切换有可能需要,用 CompositionLocal 提供更好。
桌面项目直接用一个全局对象来管理数据库连接、http客户端等
object AppGlobals {
val database by lazy {
Database.connect(
url = "jdbc:sqlite:data.db",
)
}
val userService by lazy {
UserService(database)
}
}
然后在ui页面中读取并显示
@Composable
fun ExposedTest() {
val scope = rememberCoroutineScope()
var users by remember { mutableStateOf(listOf<DbUser>()) }
var loading by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
runCatching {
users = AppGlobals.userService.read()
loading = false
}.onFailure {
it.printStackTrace()
}
}
}
Column {
if(loading) {
Text("Loading...")
}else {
users.forEach { user ->
Spacer(Modifier.height(40.dp))
Text(user.name, style = MaterialTheme.typography.bodyMedium)
}
}
Button(onClick = {
scope.launch {
withContext(Dispatchers.IO) {
AppGlobals.userService.create(
DbUser(
"user_${System.currentTimeMillis()}",
"passw0rd"
)
)
}
}
}) {
Text("Add")
}
}
}
嗯,kotlin确实很优雅~