跳到主要内容

Room 数据库

Room 是 Google 官方推出的 SQLite 抽象层,是 Android Jetpack 架构组件的一部分。它提供了编译时 SQL 检查、流畅的 API 和与 LiveData、Flow 的无缝集成。

什么是 Room?

Room 在 SQLite 之上提供了一个抽象层,让数据库操作更加安全和便捷:

  • 编译时 SQL 验证:SQL 语句在编译时检查,避免运行时错误
  • 减少样板代码:自动生成 DAO 实现,无需手写大量代码
  • 与 Jetpack 集成:原生支持 LiveData、Flow、ViewModel
  • 类型安全:使用注解处理器确保类型安全

Room 三大核心组件

  1. Entity:数据实体类,对应数据库表
  2. DAO(Data Access Object):数据访问对象,定义数据库操作
  3. Database:数据库持有者,连接 Entity 和 DAO

添加依赖

在 build.gradle.kts 中添加 Room 依赖:

dependencies {
val roomVersion = "2.6.1"

// Room 运行时
implementation("androidx.room:room-runtime:$roomVersion")

// Room Kotlin 扩展和协程支持
implementation("androidx.room:room-ktx:$roomVersion")

// 编译时注解处理器
kapt("androidx.room:room-compiler:$roomVersion")

// 可选:测试支持
testImplementation("androidx.room:room-testing:$roomVersion")
}

定义 Entity

Entity 使用 @Entity 注解,每个类对应数据库中的一张表。

@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,

@ColumnInfo(name = "user_name")
val userName: String,

@ColumnInfo(name = "email")
val email: String,

@ColumnInfo(name = "age")
val age: Int = 0,

@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis()
)

Entity 注解详解

@Entity(
tableName = "products", // 自定义表名
indices = [ // 创建索引
Index(value = ["name"]),
Index(value = ["category_id", "price"])
],
foreignKeys = [ // 外键约束
ForeignKey(
entity = Category::class,
parentColumns = ["id"],
childColumns = ["category_id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Product(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,

val name: String,

@ColumnInfo(defaultValue = "0.0")
val price: Double,

@ColumnInfo(name = "category_id")
val categoryId: Int,

@Ignore // 忽略此字段,不存入数据库
val temporaryData: String = ""
)

复合主键

@Entity(
primaryKeys = ["studentId", "courseId"]
)
data class Enrollment(
val studentId: Int,
val courseId: Int,
val enrollmentDate: Long
)

定义 DAO

DAO(Data Access Object)定义了访问数据库的方法,使用接口或抽象类。

@Dao
interface UserDao {

// ==================== 插入操作 ====================

@Insert
suspend fun insert(user: User): Long

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(user: User): Long

@Insert
suspend fun insertAll(users: List<User>): List<Long>

// ==================== 更新操作 ====================

@Update
suspend fun update(user: User): Int

@Update
suspend fun updateAll(users: List<User>): Int

// ==================== 删除操作 ====================

@Delete
suspend fun delete(user: User): Int

@Delete
suspend fun deleteAll(users: List<User>): Int

@Query("DELETE FROM users WHERE id = :userId")
suspend fun deleteById(userId: Int): Int

@Query("DELETE FROM users")
suspend fun deleteAllUsers()

// ==================== 查询操作 ====================

@Query("SELECT * FROM users")
suspend fun getAll(): List<User>

@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getById(userId: Int): User?

@Query("SELECT * FROM users WHERE age > :minAge")
suspend fun getUsersOlderThan(minAge: Int): List<User>

@Query("SELECT * FROM users WHERE user_name LIKE '%' || :name || '%'")
suspend fun searchByName(name: String): List<User>

@Query("SELECT COUNT(*) FROM users")
suspend fun getUserCount(): Int
}

使用 LiveData 和 Flow

@Dao
interface UserDao {

// 返回 LiveData,数据变化时自动通知 UI
@Query("SELECT * FROM users")
fun getAllUsersLiveData(): LiveData<List<User>>

@Query("SELECT * FROM users WHERE id = :userId")
fun getUserByIdLiveData(userId: Int): LiveData<User?>

// 返回 Flow,支持协程流式操作
@Query("SELECT * FROM users")
fun getAllUsersFlow(): Flow<List<User>>

@Query("SELECT * FROM users WHERE age > :minAge")
fun getUsersOlderThanFlow(minAge: Int): Flow<List<User>>
}

事务操作

@Dao
abstract class ProductDao {

@Transaction
open suspend fun updateProductAndInventory(
product: Product,
inventoryChange: Int
) {
update(product)
updateInventory(product.id, inventoryChange)
}

@Update
abstract suspend fun update(product: Product)

@Query("UPDATE inventory SET quantity = quantity + :change WHERE product_id = :productId")
abstract suspend fun updateInventory(productId: Int, change: Int)
}

定义 Database

@Database(
entities = [User::class, Product::class, Category::class],
version = 1,
exportSchema = false // 生产环境建议设置为 true
)
abstract class AppDatabase : RoomDatabase() {

abstract fun userDao(): UserDao
abstract fun productDao(): ProductDao
abstract fun categoryDao(): CategoryDao

companion object {
@Volatile
private var INSTANCE: AppDatabase? = null

fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also {
INSTANCE = it
}
}
}

private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database.db"
)
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// 数据库首次创建时执行
}
})
.build()
}
}
}

使用 Hilt 注入数据库

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database.db"
).build()
}

@Provides
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}

@Provides
fun provideProductDao(database: AppDatabase): ProductDao {
return database.productDao()
}
}

// 在 ViewModel 中使用
@HiltViewModel
class UserViewModel @Inject constructor(
private val userDao: UserDao
) : ViewModel() {
// ...
}

数据库迁移

当数据库结构发生变化时,需要进行迁移:

// 版本 1 到版本 2:添加新列
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"ALTER TABLE users ADD COLUMN phone_number TEXT DEFAULT ''"
)
}
}

// 版本 2 到版本 3:创建新表
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
total_amount REAL NOT NULL,
created_at INTEGER NOT NULL
)
""".trimIndent()
)
}
}

// 应用迁移
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()

破坏性迁移(开发时使用)

Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.fallbackToDestructiveMigration() // 数据丢失,仅开发使用
.build()

关系查询

一对一关系

@Entity
data class User(
@PrimaryKey val userId: Long,
val name: String
)

@Entity
data class LibraryCard(
@PrimaryKey val cardId: Long,
val userId: Long, // 外键
val cardNumber: String
)

data class UserAndLibraryCard(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "userId"
)
val libraryCard: LibraryCard
)

@Dao
interface UserDao {
@Transaction
@Query("SELECT * FROM user")
suspend fun getUsersWithLibraryCards(): List<UserAndLibraryCard>
}

一对多关系

@Entity
data class User(
@PrimaryKey val userId: Long,
val name: String
)

@Entity
data class Book(
@PrimaryKey val bookId: Long,
val userId: Long, // 外键
val title: String
)

data class UserWithBooks(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "userId"
)
val books: List<Book>
)

@Dao
interface UserDao {
@Transaction
@Query("SELECT * FROM user WHERE userId = :userId")
suspend fun getUserWithBooks(userId: Long): UserWithBooks
}

多对多关系

@Entity
data class Student(
@PrimaryKey val studentId: Long,
val name: String
)

@Entity
data class Course(
@PrimaryKey val courseId: Long,
val name: String
)

@Entity(primaryKeys = ["studentId", "courseId"])
data class StudentCourseCrossRef(
val studentId: Long,
val courseId: Long
)

data class StudentWithCourses(
@Embedded val student: Student,
@Relation(
parentColumn = "studentId",
entityColumn = "courseId",
associateBy = Junction(StudentCourseCrossRef::class)
)
val courses: List<Course>
)

最佳实践

1. 使用 Repository 模式

class UserRepository @Inject constructor(
private val userDao: UserDao
) {
fun getAllUsers(): Flow<List<User>> = userDao.getAllUsersFlow()

suspend fun insertUser(user: User) = userDao.insert(user)

suspend fun updateUser(user: User) = userDao.update(user)

suspend fun deleteUser(user: User) = userDao.delete(user)
}

2. 在 ViewModel 中使用

@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {

val allUsers: LiveData<List<User>> = repository.getAllUsers()
.asLiveData()

fun addUser(user: User) {
viewModelScope.launch {
repository.insertUser(user)
}
}

fun updateUser(user: User) {
viewModelScope.launch {
repository.updateUser(user)
}
}

fun deleteUser(user: User) {
viewModelScope.launch {
repository.deleteUser(user)
}
}
}

3. 数据库预填充

Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.createFromAsset("database/myapp.db") // 从 assets 加载
.createFromFile(File("database/myapp.db")) // 从文件加载
.build()

小结

  1. Entity:使用 @Entity 定义数据表结构
  2. DAO:使用 @Dao 定义数据库操作方法
  3. Database:使用 @Database 创建数据库实例
  4. 关系查询:使用 @Relation@Embedded 处理表关系
  5. 迁移:使用 Migration 处理数据库版本升级
  6. 最佳实践:配合 Repository 和 ViewModel 使用

练习

  1. 创建一个包含 User 表的 Room 数据库
  2. 实现增删改查功能,使用 Flow 返回数据
  3. 创建一对多关系(用户和订单)
  4. 实现数据库迁移,添加新列