使用Room将数据保存到本地数据库
简介
Room持久库在SQLite之上提供了一个抽象层,以便在充分利用SQLite的强大功能的同时,能够流畅的访问数据库,具体来说,Room有以下优点:
- SQL查询的编译时验证
- 提供方便的注解,能最大减少重复和易错的样板代码
- 简化了数据库迁移路径
因此建议使用Room,而不是直接操作SQLite API。
设置
在 build.gradle 中,添加以下依赖:
//Kotlin annotation processing tool plugin
apply plugin: 'kotlin-kapt'
dependencies {
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}
主要组件
Room包含了三个主要的组件:
- 数据库类
androidx.room.Database : 此对象持有数据库,并为应用持久化数据的底层连接提供主要的入口。 - 数据实体
Entity : 对应数据库表。 - 数据访问对象
DAO : 一个接口或抽象类,提供数据库的CRUD 抽象操作方法,编译时Room会自动生成DAO 对应的实现实例。
androidx.room.Database 为app提供了关联数据库的DAO 实例。因此,app可以使用这些DAO 方法从数据库检索数据,以作为关联的数据实体对象的实例。此外,app还可以使用定义的数据实体对关联的表进行插入或更新单行或多行操作。下图展示了Room各组件间的关系:
示例
本部分展示了真实项目开发中的一个缩影。
项目中新建数据库相关的包
dao :存放数据访问对象database :存放数据库对象entity : 存放数据表实体
数据实体类
以下代码通过注解@Entity 提供了一个数据实体LoginUser ,每个LoginUser 实例都代表tbl_login_user 表中的一行数据。
@Entity(tableName = "tbl_login_user")
data class LoginUser(
@PrimaryKey
@ColumnInfo(name = "user_id")
val userId: String,
@ColumnInfo(name = "login_name")
val loginName: String?,
@ColumnInfo(name = "user_name")
val userName: String?,
@ColumnInfo(name = "avatar")
val avatar: String?,
@ColumnInfo(name = "gender")
val gender: Int?,
@ColumnInfo(name = "pwd_encrypt")
val pwdEncrypt: String?,
@ColumnInfo(name = "phone")
val phone: String?,
@ColumnInfo(name = "third_type")
val thirdType: String?,
@ColumnInfo(name = "third_token")
val thirdToken: String?,
@ColumnInfo(name = "token")
val token: String,
@ColumnInfo(name = "refresh_token")
val refreshToken: String?,
@ColumnInfo(name = "remember_pwd")
val rememberPwd: Int,
@ColumnInfo(name = "auto_login")
val autoLogin: Int,
@ColumnInfo(name = "login_count")
val loginCount: Int,
@ColumnInfo(name = "enable")
val enable: Int,
@ColumnInfo(name = "extra")
val extra: String?,
@ColumnInfo(name = "create_timestamp")
val createTimestamp: String,
@ColumnInfo(name = "update_timestamp")
val updateTimestamp: String,
@ColumnInfo(name = "create_time")
val createTime: String,
@ColumnInfo(name = "update_time")
val updateTime: String
)
DAO
以下代码通过@Dao 注解定义了一个LoginUserDao 接口(抽象类亦可),该接口为app提供了操作tbl_login_user 表的方法。
@Dao
interface LoginUserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrReplace(vararg loginUser: LoginUser)
@Query("SELECT * FROM tbl_login_user WHERE auto_login == 1 ORDER BY update_timestamp DESC LIMIT 0,1")
fun queryLatestAutoLoginUser(): LoginUser
@Query("DELETE FROM tbl_login_user WHERE user_id NOT IN (SELECT user_id FROM tbl_login_user ORDER BY update_timestamp DESC LIMIT 0,1000)")
fun deleteOldData()
}
Room数据库类
以下的代码通过注解@Database 定义了一个持有数据库的抽象类,此类提供了数据库的配置和app访问数据库的入口。定义此类必须满足以下条件:
- 必须带有注解
@Database ,注解中需要有entities 数组,数组中有数据表对应的数据实体。 - 必须继承自
RoomDatabase ,且为抽象类。 - 必须有定义无参的DAO抽象方法。
注意: 由于数据库实例的创建很耗资源,日常的单进程app场景中,应该尽可能的采用单例模式创建Room数据库实例。
@Database(version = 1, entities = [LoginUser::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun loginUserDao(): LoginUserDao
companion object {
@Volatile
private var instance: AppDatabase? = null
@Synchronized
fun getInstance(): AppDatabase {
if (instance == null) {
instance = Room.databaseBuilder(Utils.getApp(), AppDatabase::class.java, "app_data_db")
.fallbackToDestructiveMigration()
.build()
}
return instance as AppDatabase
}
}
}
使用
通过AppDatabase 类中提供的抽象方法获取DAO 实例操作数据库:
val loginUserDao: LoginUserDao = AppDatabase.getInstance().loginUserDao()
val loginUser: LoginUser = loginUserDao.queryLatestAutoLoginUser()
|