数据库:SQLDelight
SQLDelight 是 Kotlin Multiplatform 项目中最流行的数据库解决方案之一。它通过 SQL 文件生成类型安全的 Kotlin API,支持多平台数据持久化。
基础配置
依赖配置
toml
[versions]
sqldelight = "2.0.2"
[libraries]
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
sqldelight-primitive-adapters = { module = "app.cash.sqldelight:primitive-adapters", version.ref = "sqldelight" }
# 平台驱动
sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
sqldelight-sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
[plugins]
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
[bundles]
sqldelight-common = ["sqldelight-runtime", "sqldelight-coroutines"]kotlin
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.sqldelight)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.bundles.sqldelight.common)
}
androidMain.dependencies {
implementation(libs.sqldelight.android.driver)
}
iosMain.dependencies {
implementation(libs.sqldelight.native.driver)
}
jvmMain.dependencies {
implementation(libs.sqldelight.sqlite.driver)
}
}
}
sqldelight {
databases {
create("AppDatabase") {
packageName.set("com.example.app.db")
// 指定 SQL 文件目录
srcDirs.setFrom("src/commonMain/sqldelight")
// 开启验证
verifyMigrations.set(true)
}
}
}最新版本查询:https://github.com/cashapp/sqldelight/releases
项目结构
shared/src/
└── commonMain/
└── sqldelight/
└── com/example/app/db/
├── User.sq # Users 表定义
├── Post.sq # Posts 表定义
└── migrations/ # 数据库迁移
├── 1.sqm
└── 2.sqmSQL 文件编写
sql
-- 创建表
CREATE TABLE User (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
age INTEGER NOT NULL,
created_at INTEGER NOT NULL
);
-- 创建索引
CREATE INDEX user_email_idx ON User(email);
-- 插入数据
insertUser:
INSERT INTO User(name, email, age, created_at)
VALUES (?, ?, ?, ?);
-- 查询所有用户
selectAll:
SELECT * FROM User;
-- 按 ID 查询
selectById:
SELECT * FROM User
WHERE id = ?;
-- 按邮箱查询
selectByEmail:
SELECT * FROM User
WHERE email = ?;
-- 更新用户
updateUser:
UPDATE User
SET name = ?, email = ?, age = ?
WHERE id = ?;
-- 删除用户
deleteUser:
DELETE FROM User WHERE id = ?;
-- 统计数量
countUsers:
SELECT COUNT(*) FROM User;
-- 分页查询
selectPaginated:
SELECT * FROM User
LIMIT ? OFFSET ?;
-- 复杂查询
selectUserWithPosts:
SELECT
User.*,
COUNT(Post.id) AS post_count
FROM User
LEFT JOIN Post ON Post.user_id = User.id
WHERE User.age > ?
GROUP BY User.id
ORDER BY post_count DESC;sql
CREATE TABLE Post (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE
);
-- 查询用户的所有帖子
selectByUserId:
SELECT * FROM Post
WHERE user_id = ?
ORDER BY created_at DESC;
-- 插入帖子
insertPost:
INSERT INTO Post(user_id, title,content, created_at)
VALUES(?, ?, ?, ?);
-- 删除用户的所有帖子
deleteByUserId:
DELETE FROM Post WHERE user_id = ?;驱动配置
平台特定驱动
kotlin
expect class DriverFactory {
fun createDriver(): SqlDriver
}
// 创建数据库
fun createDatabase(driverFactory: DriverFactory): AppDatabase {
val driver = driverFactory.createDriver()
return AppDatabase(driver)
}kotlin
import android.content.Context
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
actual class DriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver(
schema = AppDatabase.Schema,
context = context,
name = "app.db"
)
}
}
// 使用
val database = createDatabase(DriverFactory(context))kotlin
import app.cash.sqldelight.driver.native.NativeSqliteDriver
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(
schema = AppDatabase.Schema,
name = "app.db"
)
}
}kotlin
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import app.cash.sqldelight.db.SqlDriver
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).also {
AppDatabase.Schema.create(it)
}
}
}数据库操作
基础 CRUD
kotlin
class UserRepository(database: AppDatabase) {
private val queries = database.userQueries
// 创建
fun createUser(name: String, email: String, age: Long) {
queries.insertUser(
name = name,
email = email,
age = age,
created_at = System.currentTimeMillis()
)
}
// 读取
fun getAllUsers(): List<User> {
return queries.selectAll().executeAsList()
}
fun getUserById(id: Long): User? {
return queries.selectById(id).executeAsOneOrNull()
}
// 更新
fun updateUser(id: Long, name: String, email: String, age: Long) {
queries.updateUser(
name = name,
email = email,
age = age,
id = id
)
}
// 删除
fun deleteUser(id: Long) {
queries.deleteUser(id)
}
}事务支持
kotlin
fun transferData(from: Long, to: Long) {
database.transaction {
// 所有操作在同一事务中
queries.updateBalance(from, -100)
queries.updateBalance(to, 100)
}
}
// 事务回滚
fun safeOperation() {
try {
database.transaction {
queries.insertUser(/* ... */)
queries.insertPost(/* ... */)
// 如果抛出异常,事务会自动回滚
}
} catch (e: Exception) {
// 处理错误
}
}Flow 与协程集成
依赖配置
kotlin
dependencies {
implementation("app.cash.sqldelight:coroutines-extensions:2.0.2")
}标准代码块
kotlin
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import app.cash.sqldelight.coroutines.mapToOne
import app.cash.sqldelight.coroutines.mapToOneOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
class UserRepository(database: AppDatabase) {
private val queries = database.userQueries
// 观察所有用户
fun observeAllUsers(): Flow<List<User>> {
return queries.selectAll()
.asFlow()
.mapToList(Dispatchers.IO)
}
// 观察单个用户
fun observeUser(id: Long): Flow<User?> {
return queries.selectById(id)
.asFlow()
.mapToOneOrNull(Dispatchers.IO)
}
// 在协程中执行
suspend fun createUserSuspend(name: String, email: String, age: Long) {
withContext(Dispatchers.IO) {
queries.insertUser(name, email, age, System.currentTimeMillis())
}
}
}
// 在 ViewModel 中使用
class UserViewModel(private val repository: UserRepository) {
val users: Flow<List<User>> = repository.observeAllUsers()
fun addUser(name: String, email: String, age: Long) {
viewModelScope.launch {
repository.createUserSuspend(name, email, age)
}
}
}数据库迁移
迁移文件
src/commonMain/sqldelight/migrations/
├── 1.sqm # 初始版本
├── 2.sqm # 添加新列
└── 3.sqm # 创建新表sql
CREATE TABLE User (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL
);sql
ALTER TABLE User ADD COLUMN age INTEGER NOT NULL DEFAULT 0;
ALTER TABLE User ADD COLUMN created_at INTEGER NOT NULL DEFAULT 0;sql
CREATE TABLE Post (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES User(id)
);运行迁移
kotlin
// 配置自动迁移
sqldelight {
databases {
create("AppDatabase") {
packageName.set("com.example.app.db")
verifyMigrations.set(true) // 验证迁移正确性
}
}
}
// 手动迁移(如需要)
val driver = AndroidSqliteDriver(
schema = AppDatabase.Schema,
context = context,
name = "app.db",
callback = object : AndroidSqliteDriver.Callback(AppDatabase.Schema) {
override fun onOpen(db: SupportSQLiteDatabase) {
// 迁移后的操作
}
}
)类型适配器
自定义类型
kotlin
import app.cash.sqldelight.ColumnAdapter
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
// 列表类型适配器
object StringListAdapter : ColumnAdapter<List<String>, String> {
override fun decode(databaseValue: String): List<String> {
return if (databaseValue.isEmpty()) {
emptyList()
} else {
Json.decodeFromString(databaseValue)
}
}
override fun encode(value: List<String>): String {
return Json.encodeToString(value)
}
}
// Enum 适配器
enum class UserStatus { ACTIVE, INACTIVE, BANNED }
object UserStatusAdapter : ColumnAdapter<UserStatus, String> {
override fun decode(databaseValue: String) = UserStatus.valueOf(databaseValue)
override fun encode(value: UserStatus) = value.name
}
// 注册适配器
val database = AppDatabase(
driver = driver,
UserAdapter = User.Adapter(
tagsAdapter = StringListAdapter,
statusAdapter = UserStatusAdapter
)
)sql
-- User.sq with custom types
CREATE TABLE User (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
tags TEXT AS List<String> NOT NULL, -- 自定义类型
status TEXT AS UserStatus NOT NULL -- Enum 类型
);最佳实践
✅ 实践 1:单例数据库
kotlin
object DatabaseProvider {
private var database: AppDatabase? = null
fun getDatabase(driverFactory: DriverFactory): AppDatabase {
return database ?: synchronized(this) {
database ?: createDatabase(driverFactory).also { database = it }
}
}
}✅ 实践 2:Repository 模式
kotlin
// 数据层
interface UserRepository {
fun observeUsers(): Flow<List<User>>
suspend fun addUser(user: User)
suspend fun getUser(id: Long): User?
}
class UserRepositoryImpl(private val database: AppDatabase) : UserRepository {
private val queries = database.userQueries
override fun observeUsers(): Flow<List<User>> {
return queries.selectAll().asFlow().mapToList(Dispatchers.IO)
}
override suspend fun addUser(user: User) = withContext(Dispatchers.IO) {
queries.insertUser(user.name, user.email, user.age, user.created_at)
}
override suspend fun getUser(id: Long): User? = withContext(Dispatchers.IO) {
queries.selectById(id).executeAsOneOrNull()
}
}✅ 实践 3:使用索引优化查询
sql
-- 为常用查询字段添加索引
CREATE INDEX user_email_idx ON User(email);
CREATE INDEX post_user_id_idx ON Post(user_id);
CREATE INDEX post_created_at_idx ON Post(created_at DESC);✅ 实践 4:批量插入优化
kotlin
fun insertUsers(users: List<User>) {
database.transaction {
users.forEach { user ->
queries.insertUser(
user.name,
user.email,
user.age,
user.created_at
)
}
}
}❌ 避免在主线程执行
kotlin
// ❌ 不推荐
fun getUsers(): List<User> {
return queries.selectAll().executeAsList() // 阻塞主线程
}
// ✅ 推荐
suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
queries.selectAll().executeAsList()
}
// 或使用 Flow
fun observeUsers(): Flow<List<User>> {
return queries.selectAll().asFlow().mapToList(Dispatchers.IO)
}测试
内存数据库测试
kotlin
class UserRepositoryTest {
private lateinit var database: AppDatabase
private lateinit var repository: UserRepository
@Before
fun setup() {
// 使用内存数据库
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
AppDatabase.Schema.create(driver)
database = AppDatabase(driver)
repository = UserRepositoryImpl(database)
}
@After
fun teardown() {
database.close()
}
@Test
fun testInsertAndQuery() = runTest {
repository.addUser(User(0, "Alice", "alice@example.com", 25, 0))
val users = repository.getUsers()
assertEquals(1, users.size)
assertEquals("Alice", users[0].name)
}
}SQLDelight 提供了类型安全、跨平台的数据库解决方案。通过 SQL 文件生成 Kotlin API,既保留了 SQL 的灵活性,又享受了编译时类型检查的好处。