Room 数据库
Room 是 Google 官方推出的 SQLite 抽象层,是 Android Jetpack 架构组件的一部分。它提供了编译时 SQL 检查、流畅的 API 和与 LiveData、Flow 的无缝集成。
什么是 Room?
Room 在 SQLite 之上提供了一个抽象层,让数据库操作更加安全和便捷:
- 编译时 SQL 验证:SQL 语句在编译时检查,避免运行时错误
- 减少样板代码:自动生成 DAO 实现,无需手写大量代码
- 与 Jetpack 集成:原生支持 LiveData、Flow、ViewModel
- 类型安全:使用注解处理器确保类型安全
Room 三大核心组件
- Entity:数据实体类,对应数据库表
- DAO(Data Access Object):数据访问对象,定义数据库操作
- 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()
小结
- Entity:使用
@Entity定义数据表结构 - DAO:使用
@Dao定义数据库操作方法 - Database:使用
@Database创建数据库实例 - 关系查询:使用
@Relation和@Embedded处理表关系 - 迁移:使用
Migration处理数据库版本升级 - 最佳实践:配合 Repository 和 ViewModel 使用
练习
- 创建一个包含 User 表的 Room 数据库
- 实现增删改查功能,使用 Flow 返回数据
- 创建一对多关系(用户和订单)
- 实现数据库迁移,添加新列