Jetpack 架构组件:Room 数据库应用

简介

Room 是一个 ORM (Object Relational Mapping)对象关系映射数据库、其底层还是封装的 SQLite 的能力。它赋予了一个强大的功能,就是可以用面向对象的思维来和数据库进行交互,绝大数情况下不用再和SQL语句打交道了,同时也不用担心操作数据库的逻辑会让项目的整体代码变混乱。

Room 主要是由Entity、Dao 和Database 这3部分组成:
在这里插入图片描述

  • Entity

用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。

  • Dao

Dao 是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。

  • Database

用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提供Dao层的访问实例。

依赖

apply plugin: 'kotlin-kapt'

dependencies {
    def roomVersion = "2.2.0"
    // Room
    implementation("androidx.room:room-runtime:$roomVersion")
    // java -Room
    annotationProcessor("androidx.room:room-compiler:$roomVersion")
    // kotlin -Room
    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
)
  • @PrimaryKey

每个实体必须将至少1个字段定义为主键。即使只有1个字段,仍然需要为该字段添加@PrimaryKey注释。此外,如果想让Room为实体分配自动 ID,则可以设置@PrimaryKey的autoGenerate 属性。

  • tableName

默认情况下,Room将类名称用作数据库表名称。如果希望表具有不同的名称,请设置 @Entity 注释的 tableName 属性。
注意: SQLite 中的表名称不区分大小写。

  • @ColumnInfo

默认情况下,Room将字段名称用作数据库中的列名称。如果您希望列具有不同的名称,请将 @ColumnInfo 注释添加到字段

DAO(数据访问对象)

DAO 既可以是接口,也可以是抽象类。如果是抽象类,则该 DAO 可以选择有一个以RoomDatabase为唯一参数的构造函数。Room 会在编译时创建每个 DAO 实现。

插入

当您创建 DAO 方法并使用 @Insert 对其进行注释时,Room 会生成一个实现,该实现在单个事务中将所有参数插入数据库中。

@Dao
interface BookDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertBook( book: Book):Long
}

// Activity 代码
tv_insert.setOnClickListener {
            thread {
                bookDao.insertBook(book)
            }
    
}

如果 @Insert 方法只接收 1 个参数,则它可以返回 long,这是插入项的新rowId。如果参数是数组或集合,则应返回 long[] 或 List

更新
@Dao
interface BookDao {
    @Update
    fun updateBooks( book: Book)
}

// Activity 代码
tv_update.setOnClickListener {
            thread {
                bookDao.updateBooks(book)
            }
    
}

可以让此方法返回一个 int 值,以指示数据库中更新的行数。

删除

Delete 便捷方法会从数据库中删除一组以参数形式给出的实体。它使用主键查找要删除的实体

@Dao
interface BookDao {
    @Delete
    fun deleteBooks( book: Book)
}

// Activity 代码
tv_delete.setOnClickListener {
            thread {
                bookDao.deleteBooks(book)
            }
        }

可以让此方法返回一个int值,以指示从数据库中删除的行数。

查询

@Query 是 DAO 类中使用的主要注释。它允许您对数据库执行读/写操作。每个@Query方法都会在编译时进行验证,因此如果查询出现问题,则会发生编译错误,而不是运行时失败

  • 简单查询
@Dao
interface BookDao {
    @Query("SELECT * FROM book ")
    fun selectAllBooks(): List<Book>
}

//Activity 中代码
 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>
  • 使用 LiveData 进行可观察查询
@Dao
interface BookDao {
    fun selectBooksByBookName(name: String): LiveData<Book>

}

// Activity 代码
bookDao.selectBooksByBookName("数据结构与算法").observe(this, { book->
            tv_show.text="书名${book.bookName}价格:${book.bookPrice}"
        })

执行查询时,您通常会希望应用的界面在数据发生变化时自动更新。使用 LiveData 类型的返回值。当数据库更新时,Room 会生成更新 LiveData 所必需的所有代码。

  • 使用 Flow 进行可观察查询
    @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
    
// Activity 中代码
 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
)
  • 修改AppDatabase代码,升级数据库中表
@Database(entities = [Book::class], version = 2)
abstract class AppDatabase : RoomDatabase() {

    abstract fun getBookDao(): BookDao

    companion object {
        private var instance: AppDatabase? = null
        // 实现Migration 的匿名类
        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的匿名类。

增加新表
  • 新增一个数据类Press
@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
                    }
        }
    }
}

数据库查看数据

  • 导出数据库文件app_database.db

打开Device File Explorer,打开目录 data/data/包名/databases/ app_database.db,如图所示:
在这里插入图片描述
保存app_database.db 到指定位置:
在这里插入图片描述

  • 安装 DB Navigator 查看数据

在AndroidStudio中 settings->plugins 下载插件DB Navigator。
在这里插入图片描述
安装重启后,会出现如图所示:
在这里插入图片描述
连接数据库,如下图所示:
在这里插入图片描述
打开导出数据库文件,如下图所示:
在这里插入图片描述
查看数据,如下图所示:在这里插入图片描述
在这里插入图片描述

参考资料

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值