Skip to content

compose和数据库

Published:

研究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确实很优雅~


Next Post
wordpress m3u8播放器