简介
Room 是一个 ORM (Object Relational Mapping)对象关系映射数据库、其底层还是封装的 SQLite 的能力。它赋予了一个强大的功能,就是可以用面向对象的思维来和数据库进行交互,绝大数情况下不用再和SQL语句打交道了,同时也不用担心操作数据库的逻辑会让项目的整体代码变混乱。
Room 主要是由Entity、Dao 和Database 这3部分组成:
用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
Dao 是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提供Dao层的访问实例。
依赖
apply plugin: 'kotlin-kapt'
dependencies {
def roomVersion = "2.2.0"
implementation("androidx.room:room-runtime:$roomVersion")
annotationProcessor("androidx.room:room-compiler:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
}
具体请看可选依赖
用法
定义 Entity
对于每个实体,系统会在关联的Database对象中创建一个表,以存储这些项。您必须通过 Database 类中的 entities 数组引用实体类。
@Entity(tableName = "book")
data class Book(
@PrimaryKey(autoGenerate = true)
var id:Int=1,
@ColumnInfo(name = "book_name")
var bookName:String,
@ColumnInfo(name = "category_name")
var categoryName:String,
@ColumnInfo(name = "book_price")
var bookPrice:Int
)
每个实体必须将至少1个字段定义为主键。即使只有1个字段,仍然需要为该字段添加@PrimaryKey注释。此外,如果想让Room为实体分配自动 ID,则可以设置@PrimaryKey的autoGenerate 属性。
默认情况下,Room将类名称用作数据库表名称。如果希望表具有不同的名称,请设置 @Entity 注释的 tableName 属性。 注意: SQLite 中的表名称不区分大小写。
默认情况下,Room将字段名称用作数据库中的列名称。如果您希望列具有不同的名称,请将 @ColumnInfo 注释添加到字段
DAO(数据访问对象)
DAO 既可以是接口,也可以是抽象类。如果是抽象类,则该 DAO 可以选择有一个以RoomDatabase为唯一参数的构造函数。Room 会在编译时创建每个 DAO 实现。
插入
当您创建 DAO 方法并使用 @Insert 对其进行注释时,Room 会生成一个实现,该实现在单个事务中将所有参数插入数据库中。
@Dao
interface BookDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBook( book: Book):Long
}
tv_insert.setOnClickListener {
thread {
bookDao.insertBook(book)
}
}
如果 @Insert 方法只接收 1 个参数,则它可以返回 long,这是插入项的新rowId。如果参数是数组或集合,则应返回 long[] 或 List
更新
@Dao
interface BookDao {
@Update
fun updateBooks( book: Book)
}
tv_update.setOnClickListener {
thread {
bookDao.updateBooks(book)
}
}
可以让此方法返回一个 int 值,以指示数据库中更新的行数。
删除
Delete 便捷方法会从数据库中删除一组以参数形式给出的实体。它使用主键查找要删除的实体
@Dao
interface BookDao {
@Delete
fun deleteBooks( book: Book)
}
tv_delete.setOnClickListener {
thread {
bookDao.deleteBooks(book)
}
}
可以让此方法返回一个int值,以指示从数据库中删除的行数。
查询
@Query 是 DAO 类中使用的主要注释。它允许您对数据库执行读/写操作。每个@Query方法都会在编译时进行验证,因此如果查询出现问题,则会发生编译错误,而不是运行时失败。
@Dao
interface BookDao {
@Query("SELECT * FROM book ")
fun selectAllBooks(): List<Book>
}
tv_query.setOnClickListener {
thread {
val books=bookDao.selectAllBooks()
}
}
在编译时,Room 知道它在查询用户表中的所有列。如果查询包含语法错误,或者数据库中没有用户表格,则 Room 会在您的应用编译时显示包含相应消息的错误。
@Query("SELECT * FROM book WHERE category_name IN (:categorys)")
fun selectBooksByCategory(categorys: List<String>): List<Book>
@Query("SELECT * FROM book WHERE book_price > :price")
fun selectBooksByPrice(price: Int): List<Book>
@Dao
interface BookDao {
fun selectBooksByBookName(name: String): LiveData<Book>
}
bookDao.selectBooksByBookName("数据结构与算法").observe(this, { book->
tv_show.text="书名${book.bookName}价格:${book.bookPrice}"
})
执行查询时,您通常会希望应用的界面在数据发生变化时自动更新。使用 LiveData 类型的返回值。当数据库更新时,Room 会生成更新 LiveData 所必需的所有代码。
@Query("SELECT * FROM press WHERE press_name = :name")
fun selectPressByPressName(name: String): Flow<Press>
@Query("SELECT * FROM press WHERE press_name = :name")
suspend fun selectPressByPressName(name: String): Press
lifecycle.coroutineScope.launch{
val press=pressDao.selectPressByPressName("电子出版社")
}
selectPressByPressName() 是一个挂起函数,挂起函数必须在协程中或者挂起函数中使用。
注意: 除非已对构建器调用allowMainThreadQueries()(正式开发环境不建议使用此方法),否则 Room 不支持在主线程上访问数据库,因为它可能会长时间锁定界面。异步查询(返回 LiveData 或 Flowable 实例的查询)无需遵守此规则,因为此类查询会根据需要在后台线程上异步运行查询。
数据库 Database
@Database(entities = [Book::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getBookDao(): BookDao
companion object {
private var instance: AppDatabase? = null
fun getDatabase(mContext: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(mContext.applicationContext,
AppDatabase::class.java, "app_database")
.build().apply {
instance = this
instance
}
}
}
}
注意: 如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。每个 RoomDatabase 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。
如果您的应用在多个进程中运行,请在数据库构建器调用中包含 enableMultiInstanceInvalidation()。这样,如果您在每个进程中都有一个AppDatabase实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中 AppDatabase 的实例。
数据库升级
现有表增加新列
- Book 数据类增加了bookAuthor(作者)属性
@Entity(tableName = "book")
data class Book(
@PrimaryKey(autoGenerate = true)
var id:Int=1,
@ColumnInfo(name = "book_name")
var bookName:String,
@ColumnInfo(name = "category_name")
var categoryName:String,
@ColumnInfo(name = "book_price")
var bookPrice:Int,
@ColumnInfo(name = "book_author")
var bookAuthor:String
)
@Database(entities = [Book::class], version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun getBookDao(): BookDao
companion object {
private var instance: AppDatabase? = null
val MIGRATION_1_2=object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table book add column book_author text not null default 'unknown' ")
}
}
fun getDatabase(mContext: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(mContext.applicationContext,
AppDatabase::class.java, "app_database.db")
.addMigrations(MIGRATION_1_2)
.build().apply {
instance = this
instance
}
}
}
}
在@Database 注解中,将版本号升级成了2,在companion object结构体中实现了Migration的匿名类。
增加新表
@Entity(tableName = "press")
data class Press(
@PrimaryKey
var id:Int,
@ColumnInfo(name = "press_name")
var name:String ,
@ColumnInfo(name = "press_address")
var address:String
)
@Database(entities = [Book::class,Press::class], version = 3)
abstract class AppDatabase : RoomDatabase() {
abstract fun getBookDao(): BookDao
abstract fun getPressDao(): PressDao
companion object {
private var instance: AppDatabase? = null
val MIGRATION_1_2=object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table book add column book_author text not null default 'unknown' ")
}
}
val MIGRATION_2_3=object :Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table press(id integer primary key not null,press_name text not null,press_address text not null)")
}
}
fun getDatabase(mContext: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(mContext.applicationContext,
AppDatabase::class.java, "app_database.db")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build().apply {
instance = this
instance
}
}
}
}
数据库查看数据
打开Device File Explorer,打开目录 data/data/包名/databases/ app_database.db,如图所示: 保存app_database.db 到指定位置:
在AndroidStudio中 settings->plugins 下载插件DB Navigator。 安装重启后,会出现如图所示: 连接数据库,如下图所示: 打开导出数据库文件,如下图所示: 查看数据,如下图所示:
参考资料
|