1.简介
Room
是Google提供的Android架构组件之一,旨在简化数据库操作。它是SQLite
的一个抽象层,提供了更易用和安全的API。
Room的总体架构:
2.Room数据库的基础概念
Entity
Entity是Room中的数据表,每个Entity类对应一个SQLite表。
DAO (Data Access Object)
DAO是用于访问数据库的方法接口,定义了与数据库交互的操作。
Database
Database是Room数据库的抽象类,持有数据库并作为数据访问的主要入口点。
3.Room数据库的配置
添加依赖
在build.gradle文件中添加Room的依赖项。
dependencies {
implementation "androidx.room:room-runtime:2.5.0"
annotationProcessor "androidx.room:room-compiler:2.5.0"
// 可选 - 支持Lifecycle的LiveData
implementation "androidx.room:room-ktx:2.5.0"
}
定义Entity
// tableName 指定了数据库中对应的表名为 "users"。如果不指定,默认使用类名作为表名
@Entity(tableName = "users")
data class User(
// 使用默认值 0,autoGenerate = true 表示自动生成主键
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
// 如果不使用 @ColumnInfo 注解,默认情况下 Room 将使用属性名作为数据库中的列名
@ColumnInfo(name = "first_name")
val firstName: String,
@ColumnInfo(name = "last_name")
val lastName: String
)
创建DAO
@Dao
interface UserDao {
@Insert
fun insert(user: User)
//@Insert(onConflict = OnConflictStrategy.REPLACE):用于定义插入操作,并指定了替换策略为 OnConflictStrategy.REPLACE。这意味着如果插入的数据在数据库中已存在(根据主键判断),则旧数据会被新数据替换。
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(user: User)
@Query("SELECT * FROM users WHERE id = :id")
fun getUserById(id: Int): User?
@Update
fun update(user: User)
@Delete
fun delete(user: User)
}
tips: OnConflictStrategy.REPLACE
:如果插入的数据在数据库中已存在(即主键冲突),则会替换原有的数据。
创建Database
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class UserRoomDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: ItemRoomDatabase? = null
fun getDatabase(context: Context): UserRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
UserRoomDatabase::class.java,
"user_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
}
tips:注意fallbackToDestructiveMigration()
一般在调试中使用,如果你修改了数据库表结构,而没有升级数据库通常程序再次运行会报错,使用fallbackToDestructiveMigration()
表示将老的数据库表结构和数据全部删除,使用新的结构,允许破坏性迁移,即销毁旧数据库并创建新数据库。
初始化数据库
val db: UserRoomDatabase by lazy { UserRoomDatabase.getDatabase(this) }
4.Room数据库的使用
插入数据
val user = User().apply {
firstName = "John"
lastName = "Doe"
}
db.userDao().insert(user)
查询数据
val user = db.userDao().getUserById(1)
更新数据
user.lastName = "Smith"
db.userDao().update(user)
删除数据
db.userDao().delete(user)
5.Room数据库的高级特性
使用LiveData和Flow
@Query("SELECT * FROM users")
LiveData<List<User>> getAllUsers();
@Query("SELECT * FROM users")
Flow<List<User>> getAllUsersFlow();
数据库迁移
@Database(entities = [User::class], version = 2, exportSchema = false)
abstract class UserRoomDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: ItemRoomDatabase? = null
//迁移代码 用于从版本 1 迁移到版本 2。
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
//为users表增加age属性
database.execSQL("ALTER TABLE users ADD COLUMN age INTEGER")
}
}
fun getDatabase(context: Context): UserRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
UserRoomDatabase::class.java,
"user_database"
)
.addMigrations(AppDatabase.MIGRATION_1_2) //在此处添加
.build()
INSTANCE = instance
instance
}
}
}
}
使用TypeConverters
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class UserRoomDatabase: RoomDatabase() {
abstract fun userDao(): UserDao
...
}
object Converters {
@TypeConverter
@JvmStatic
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
@JvmStatic
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
User 中增加 Date
类型 createdAt
属性
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String,
val createdAt: Date
)
说明:
类型转换器 (Converters)
:使用 Room 持久化库时,有时需要在数据库存储和应用程序中的对象之间进行转换。例如,将 Date 对象存储为 Long 类型的时间戳或从时间戳恢复为 Date 对象,其他对象类型同理。@TypeConverter 注解
:用于标记类型转换器的方法,告诉 Room 如何在持久化过程中执行对象到数据库兼容格式之间的转换。@Database 和 @TypeConverters 注解
:用于在UserRoomDatabase
中指定数据库的配置,包括数据库版本号和要使用的类型转换器。
示例:
class Converters {
//enum 类型
@TypeConverter
fun toDownloadStatus(value: String): DownloadStatus = enumValueOf(value)
@TypeConverter
fun fromDownloadStatus(status: DownloadStatus): String = status.name
@TypeConverter
fun fromHashMap(value: HashMap<Int, Int>): String {
val gson = Gson()
return gson.toJson(value)
}
@TypeConverter
fun toHashMap(value: String): HashMap<Int, Int> {
val gson = Gson()
val type = object : TypeToken<HashMap<Int, Int>>() {}.type
return gson.fromJson(value, type)
}
//自定义对象
@TypeConverter
fun fromDownloadException(downloadException: DownloadException?): String? {
if (downloadException == null) {
return null
}
return Gson().toJson(downloadException)
}
@TypeConverter
fun toDownloadException(value: String?): DownloadException? {
if (value == null) {
return null
}
val type = object : TypeToken<DownloadException>() {}.type
return Gson().fromJson(value, type)
}
}
6. Room数据库的实践
线程管理
确保数据库操作在后台线程中完成,在主线程中操作数据库会报错。
Executors.newSingleThreadExecutor().execute {
db.userDao().insert(user)
}
数据库性能优化
- 使用批量插入和更新。
- 使用索引提高查询性能。
处理大型数据集
使用分页库(Paging Library)处理大型数据集。
@Query("SELECT * FROM users ORDER BY id ASC")
fun getAllUsers(): PagingSource<Int, User>
使用分页库需要增加依赖
implementation "androidx.paging:paging-runtime-ktx:$paging_version"
…
后面单独写篇文章介绍分页库使用,敬请期待…