Skip to content

数据库:SQLDelight

源:SQLDelight Documentation

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.sqm

SQL 文件编写

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 的灵活性,又享受了编译时类型检查的好处。