Android Jetpack 学习之旅--> Room 的使用

Posted Kevin-Dev

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Jetpack 学习之旅--> Room 的使用相关的知识,希望对你有一定的参考价值。

我们在日常的工作中,免不了和数据打交道,因此,存储数据便是一项很重要的工作,在此之前,我使用过 GreenDaoDBFlow 等优秀的 ORM 数据库框架,但是,这些框架都不是谷歌官方的,现在,我们有了谷歌官方的 Room 数据库框架,看看它能够给我们带来什么?

介绍

官网介绍:
The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.

简单来说:Room 是一个基于 SQLite 的强大数据库框架。

优点

  • 使用编译时注解,能够对 @Query@Entity 里面的 SQL 语句等进行验证。
  • SQL 语句的使用更加贴近,能够降低学习成本。
  • RxJava 2的支持(大部分都android数据库框架都支持),对LiveData的支持。
  • @Embedded 能够减少表的创建。

学习资料:

实战之旅

1. 在 app/build.gradle 添加:

apply plugin: 'kotlin-kapt'
dependencies 
    // room
    implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    implementation "androidx.room:room-ktx:$rootProject.roomVersion"
    kapt "androidx.room:room-compiler:$rootProject.roomVersion"
    androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

项目下的 build.gradle 添加:

ext 
   roomVersion = '2.1.0-alpha06'

2. 创建表(实体)

  • 用户表:
/**
 * 用户表
 */
@Entity(tableName = "user")
data class User(
    @ColumnInfo(name = "user_account") val account: String // 账号
    , @ColumnInfo(name = "user_pwd") val pwd: String // 密码
    , @ColumnInfo(name = "user_name") val name: String
    , @Embedded val address: Address // 地址
    , @Ignore val state: Int // 状态只是临时用,所以不需要存储在数据库中
) 
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    var id: Long = 0

  • 收藏记录表:
/**
 * 喜欢的球鞋
 */
@Entity(
    tableName = "fav_shoe"
    , foreignKeys = [ForeignKey(entity = Shoe::class, parentColumns = ["id"], childColumns = ["shoe_id"])
        , ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["user_id"])
    ],indices = [Index("shoe_id")]
)
data class FavouriteShoe(
    @ColumnInfo(name = "shoe_id") val shoeId: Long // 外键 鞋子的id
    , @ColumnInfo(name = "user_id") val userId: Long // 外键 用户的id
    , @ColumnInfo(name = "fav_date") val date: Date // 创建日期

) 
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    var id: Long = 0

  • 鞋表:
/**
 * 鞋表
 */
@Entity(tableName = "shoe")
data class Shoe(
    @ColumnInfo(name = "shoe_name") val name: String // 鞋名
    , @ColumnInfo(name = "shoe_description") val description: String// 描述
    , @ColumnInfo(name = "shoe_price") val price: Float // 价格
    , @ColumnInfo(name = "shoe_brand") val brand: String // 品牌
    , @ColumnInfo(name = "shoe_imgUrl") val imageUrl: String // 图片地址
) 
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    var id: Long = 0

  • 注解解释:
    @Entity
    声明这是一个表(实体),主要参数:tableName-表名、foreignKeys-外键、indices-索引。
    @ColumnInfo
    主要用来修改在数据库中的字段名。
    @PrimaryKey
    声明该字段主键并可以声明是否自动创建。
    @Ignore
    声明某个字段只是临时用,不存储在数据库中。
    @Embedded
    用于嵌套,里面的字段同样会存储在数据库中。

3. 创建 Dao
数据的增删查改。如果想声明一个 Dao,只要在抽象类或者接口加一个 @Dao 注解就行。


  • @Insert 注解声明当前的方法为新增的方法,并且可以设置当新增冲突的时候处理的方法。
@Dao
interface ShoeDao 
    // 省略...
    // 增加一双鞋子
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertShoe(shoe: Shoe)

    // 增加多双鞋子
    // 除了List之外,也可以使用数组
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertShoes(shoes: List<Shoe>)


  • @Delete注解声明当前的方法是一个删除方法。
@Dao
interface ShoeDao 
    // 省略...
    // 删除一双鞋子
    @Delete
    fun deleteShoe(shoe: Shoe)

    // 删除多个鞋子
    // 参数也可以使用数组
    @Delete
    fun deleteShoes(shoes:List<Shoe>)


  • @Update 注解声明当前方法是一个更新方法
@Dao
interface ShoeDao 
    // 省略...
    // 更新一双鞋
    @Update
    fun updateShoe(shoe:Shoe)

    // 更新多双鞋
    // 参数也可以是集合
    @Update
    fun updateShoes(shoes:Array<Shoe>)


  • Room 的查很接近原生的SQL语句。@Query注解不仅可以声明这是一个查询语句,也可以用来删除和修改,不可以用来新增。
@Dao
interface ShoeDao 

    // 查询一个
    @Query("SELECT * FROM shoe WHERE id=:id")
    fun findShoeById(id: Long): Shoe?

    // 查询多个 通过品牌查询多款鞋 
    @Query("SELECT * FROM shoe WHERE shoe_brand=:brand")
    fun findShoesByBrand(brand: String): List<Shoe>

    // 模糊查询 排序 同名鞋名查询鞋
    @Query("SELECT * FROM shoe WHERE shoe_name LIKE :name ORDER BY shoe_brand ASC")
    fun findShoesByName(name:String):List<Shoe>

    // 配合LiveData 返回所有的鞋子
    @Query("SELECT * FROM shoe")
    fun getAllShoesLD(): LiveData<List<Shoe>>

    // 配合LiveData 通过Id查询单款鞋子
    @Query("SELECT * FROM shoe WHERE id=:id")
    fun findShoeByIdLD(id: Long): LiveData<Shoe>

    // 配合RxJava 通过Id查询单款鞋子
    @Query("SELECT * FROM shoe WHERE id=:id")
    fun findShoeByIdRx(id: Long): Flowable<Shoe>

    // 省略...

  • 复合查询
    LiveData 为例
@Dao
interface ShoeDao 
    // 省略...
    // 根据收藏结合 查询用户喜欢的鞋的集合 内联查询
    @Query(
        "SELECT shoe.id,shoe.shoe_name,shoe.shoe_description,shoe.shoe_price,shoe.shoe_brand,shoe.shoe_imgUrl " +
                "FROM shoe " +
                "INNER JOIN fav_shoe ON fav_shoe.shoe_id = shoe.id " +
                "WHERE fav_shoe.user_id = :userId"
    )
    fun findShoesByUserId(userId: Long): LiveData<List<Shoe>>

4. 创建数据库
创建一个数据库对象是一件非常消耗资源,使用单例可以避免过多的资源消耗。

/**
 * 数据库文件
 */
@Database(entities = [User::class,Shoe::class,FavouriteShoe::class],version = 1,exportSchema = false)
abstract class AppDataBase:RoomDatabase() 
    // 得到UserDao
    abstract fun userDao():UserDao
    // 得到ShoeDao
    abstract fun shoeDao():ShoeDao
    // 得到FavouriteShoeDao
    abstract fun favouriteShoeDao():FavouriteShoeDao

    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,AppDataBase::class.java,"jetPackDemo-database")
                .addCallback(object :RoomDatabase.Callback()
                    override fun onCreate(db: SupportSQLiteDatabase) 
                        super.onCreate(db)

                        // 读取鞋的集合
                        val request = OneTimeWorkRequestBuilder<ShoeWorker>().build()
                        WorkManager.getInstance(context).enqueue(request)
                    
                )
                .build()
        
    

@Database注解声明当前是一个数据库文件,注解中entities变量声明数据库中的表(实体),以及其他的例如版本等变量。同时,获取的Dao也必须在数据库类中。完成之后,点击build目录下的make project,系统就会自动帮我创建AppDataBasexxxDao的实现类。

5. 简要封装
这里有必要提醒一下,在不使用 LiveData 和 RxJava 的前提下,Room 的操作是不可以放在主线程中的。

/**
 * 用户处理仓库
 */
class UserRepository private constructor(private val userDao: UserDao) 
    //...

    /**
     * 登录用户 本地数据库的查询
     */
    fun login(account: String, pwd: String):LiveData<User?>
            = userDao.login(account,pwd)

    /**
     * 注册一个用户 本地数据库的新增
     */
    suspend fun register(email: String, account: String, pwd: String):Long 
        return withContext(IO) 
             userDao.insertUser(User(account, pwd, email))
        
    

    companion object 
        @Volatile
        private var instance: UserRepository? = null
        fun getInstance(userDao: UserDao): UserRepository =
            // ...
    

现在,你就可以愉快的操作本地数据库了。

参考文章:
《Android Room 框架学习》

关注我的视频号,每天都有优质视频教学推送。

微信扫一扫下方二维码即可关注:

以上是关于Android Jetpack 学习之旅--> Room 的使用的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack 学习之旅--> Data Binding 的使用

Android Jetpack学习之旅--> Navigation 的使用

Android Jetpack 学习之旅--> ViewModel & LiveData 的使用

Android Jetpack 学习之旅--> Room 的使用

Lifecycle原理分析 ——《Android打怪升级之旅》

03Lifecycle源码分析 ——《Android打怪升级之旅》