草庐IT

Android Jetpack架构组件(七)— Room

独自闯天涯的码农 2023-03-28 原文

一、Room简介

ROOM:轻量级 ORM 数据库,本质上是一个SQLite。

ORM(Object Relational Mapping):对象关系映射
该模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

Android采用Sqlite作为数据库存储。Sqlite代码写起来繁琐且容易出错,所以开源社区里逐渐出现了各种ORM(Object Relational Mapping)库。这些开源ORM库都是为了方便Sqlite的使用,包括数据库的创建,升级,增删改查等。常见的ORM有ORMLite,GreenDAO等。Google也意识到了推出自家ORM的必要性,于是有了Room。

二、Room的使用

1、在app的build.gradle文件中导入依赖

dependencies {
    // room
    implementation "androidx.room:room-runtime:2.3.0"
    implementation "androidx.room:room-ktx:2.3.0"
    kapt 'androidx.room:room-compiler:2.3.0'

    // room-rxjava
    implementation "androidx.room:room-rxjava2:2.3.0"
    implementation "io.reactivex.rxjava2:rxandroid:2.1.0"

    // liveData
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'

    // ViewModel
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
}

2、Room的几个概念

  • Entity:
    这是一个Model类,对应于数据库中的一张表。Entity类是Sqlite表结构在Java类的映射。

  • Dao(Data Access Objects):
    数据访问对象,顾名思义,我们可以通过它来访问数据。

一个Entity代表着一张表,而每张表都需要一个Dao对象,以方便对这张表进行各种操作(增删改查)

常用的注解

Room通过一些注解来标注一些类的作用,比如Database,Dao等等。

  • @Database(entities =[],version=0)
    这个是用于标记DataBase初始化的,被标注的这个类会继承RoomDatabase,entities是一个集合,里面是所有数据库表的类。version代表数据库的版本。
  • @Entity
    代表数据库表 里面包含 @PrimaryKey主键等等注解
  • @Dao
    代表数据库操作类,里面可以使用@Query查询,@Insert插入,@Update修改,@Delete删除等注解

3、创建DataBase类

这个类被@Database标记,继承RoomDataBase,这个类是数据库类,数据库的版本,升级和各项初始化配置都在这里进行。

/**
 * (1) 使用entities来映射相关的实体类
 * (2) version来指明当前数据库的版本号
 * (3) 使用了单例模式来返回Database,以防止新建过多的实例造成内存的浪费
 *(4)Room.databaseBuilder(context,klass,name).build()来创建Database,
 *    其中第一个参数为上下文,
 *    第二个参数为当前Database的class字节码文件,
 *    第三个参数为数据库名称
 * 注意事项:默认情况下Database是不可以在主线程中进行调用的。
 *          因为大部分情况,操作数据库都还算是比较耗时的动作。
 *          如果需要在主线程调用则使用allowMainThreadQueries进行说明。
 */
@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {

    abstract fun userDao(): UserDAO

}

4、创建表

被注解@Entity标记,代表这个是一个数据库表,@PrimaryKey为主键

/**
 * (1) @Entity注解中我们传入了一个参数 tableName用来指定表的名称,如果不传默认类名为表名
 *(2)@PrimaryKey注解用来标注表的主键,并且使用autoGenerate = true 来指定了主键自增长
 *(3)@ColumnInfo注解用来标注表对应的列的信息比如表名、默认值等等
 *(4)@Ignore 注解顾名思义就是忽略这个字段,使用了这个注解的字段将不会在数据库中生成对应的列信息。也可以使用@Entity注解中的 ignoredColumns 参数来指定,效果是一样的
 */
@Entity(tableName = "users")
class UserEntity(
    @PrimaryKey(autoGenerate = false) var userId: String,
    @ColumnInfo(name = "user_name") var userName: String,
    @ColumnInfo(name = "avatar") var avatar: String
)

5、创建Dao

被@Dao注解标记,这个是数据库操作类,可以使用增删改查的一些注解标记方法。
@Query:为查询表中的数据的意思
@Update:修改的表中的数据的意思
@Insert:插入新的表数据
@Delete: 删除表的数据

@Dao
interface UserDAO {

    @Query("select * from users where userId = :id")
    fun getUserById(id: Long): UserEntity

    @Query("select * from users")
    fun getAllUsers(): List<UserEntity>

    // 参数onConflict,表示的是当插入的数据已经存在时候的处理逻辑,有三种操作逻辑:REPLACE、ABORT和IGNORE。
    // 如果不指定则默认为ABORT终止插入数据。这里我们将其指定为REPLACE替换原有数据。
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun addUser(user: UserEntity)

    @Delete
    fun deleteUserByUser(user: UserEntity)

    @Update
    fun updateUserByUser(user: UserEntity)

    // 上面说的 @Query 查询接受的参数是一个字符串,所以像删除或者更新我们也可以使用
    // @Query 注解来使用SQL语句来直接执行。比如根据userid来查询某个用户或者根据userid更新某个用户的姓名:

    @Query("delete from users where userId = :id ")
    fun deleteUserById(id: Long)

    @Query("update  users set user_name = :updateName where userID =  :id")
    fun update(id: Long, updateName: String)
}

6、数据库工具类

class DbHelper private constructor() {
    var mDatabase: AppDatabase? = null
    var currentUser: String? = null

    companion object {
        val instance: DbHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            DbHelper()
        }
    }

    /**
     * 初始化数据库
     */
    fun initDB(context: Context, user: String) {
        if (currentUser != null) {
            if (TextUtils.equals(currentUser, user)) {
                return
            }
            closeDb()
        }
        currentUser = user
        val dbName = "ring${currentUser}.db"
        mDatabase = Room.databaseBuilder(
            context.applicationContext,
            AppDatabase::class.java,
            dbName
        ).allowMainThreadQueries().build()
    }

    /**
     * 关闭数据库
     */
    fun closeDb() {
        if (mDatabase != null) {
            mDatabase!!.close()
            mDatabase = null
        }
    }

    /**
     * 获取用户访问对象
     */
    fun getUserDao(): UserDAO? {
        if (mDatabase != null) {
            return mDatabase!!.userDao()
        }
        return null
    }
}

7、数据库升级

数据库升级一般是表字段进行了修改。一般新增一些字段的情况比较多。

1.添加Entity字段

原Entity

@Entity(tableName = "users")
class UserEntity(
    @PrimaryKey(autoGenerate = false) var userId: String,
    @ColumnInfo(name = "user_name") var userName: String,
    @ColumnInfo(name = "avatar") var avatar: String
)

新Entity添加一个年龄

@Entity(tableName = "users")
class UserEntity(
    @PrimaryKey(autoGenerate = false) var userId: String,
    @ColumnInfo(name = "user_name") var userName: String,
    @ColumnInfo(name = "avatar") var avatar: String
    @ColumnInfo(name = "age") var age: Int
)
2.修改AppDataBase的版本号

原来的版本号:

@Database(entities = [UserEntity::class], version = 1)

修改后的版本号:

@Database(entities = [UserEntity::class], version = 2)

注意:version的版本号要和下面提到的MIGRATION要对应上!

3.创建MIGRATION

添加一个MIGRATION,并且添加上修改数据库表字段的sql代码:

    private val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("ALTER TABLE User ADD COLUMN age Interge")
        }
    }

注意: Migration(1, 2)代表数据库版本从1升级到2,和前一步修改数据库版本对应。

4.将创建的MIGRATION添加到数据构造中去
     /**
     * 初始化数据库
     */
    fun initDB(context: Context, user: String) {
        if (currentUser != null) {
            if (TextUtils.equals(currentUser, user)) {
                return
            }
            closeDb()
        }
        currentUser = user
        val dbName = "ring${currentUser}.db"
        mDatabase = Room.databaseBuilder(
            context.applicationContext,
            AppDatabase::class.java,
            dbName
        )
          // MIGRATION添加到这里
          .addMigrations(MIGRATION_1_2)
          .allowMainThreadQueries()
          .build()
    }

        // MIGRATION
        private val MIGRATION_1_2 = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE User ADD COLUMN age Interge")
            }
        }

如果有多个MIGRATION则往后添加:

 .addMigrations(MIGRATION_1_2, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7)

三、Room和Rxjave、LiveDate、Flow联用

1、Room与LiveData联用

class UserViewModel: ViewModel() {

    // 获取用户列表的LiveData
    var userLiveData: MutableLiveData<MutableList<UserEntity>> = MutableLiveData()

    fun getUserList(context: Context) {
        viewModelScope.launch {
            val list = AppDataBase.getInstance(context).userDao().getUsers()
            userLiveData.postValue(list)
        }
    }
}
    private fun getUserList() {
        viewModel.userLiveData.observe(this , Observer {
            //获取到用户列表
        })
    }

2、Room与Rxjava联用

如果要和Rxjava联用可以需要添加依赖:

implementation "androidx.room:room-rxjava2:2.3.0"

dao的方法需要改成这样:

    /**
     * 和Rxjava联用的获取数据
     */
    @Query("SELECT * FROM user")
    fun getUsersByRxjava() :Single<MutableList<UserEntity>>

使用的时候:

    private fun getUserListByRxJava() {
        var disposable = AppDataBase.getInstance(this)
            .userDao().getUsersByRxjava().subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                //获取到用户列表
            }, {

            })
        mDisposables?.add(disposable)
    }

Insert、update、delete操作可以返回Complete,Single,Maybe三种对象;
query操作可以返回Single,Maybe,也可以返回Observable,Flowable。

RxJave这几个对象:

  • Completable:
    只有onComplete和onError方法,即是只有“完成”和“错误”两种状态,不会返回具体的结果。
  • Single:
    其回调为onSuccess和onError,查询成功会在onSuccess中返回结果,需要注意的是,如果未查询到结果,即查询结果为空,会直接走onError回调,抛出EmptyResultSetException异常。
  • Maybe:
    其回调为onSuccess,onError,onComplete,查询成功,如果有数据,会先回调onSuccess再回调onComplete,如果没有数据,则会直接回调onComplete。
  • Flowable/Observable:
    返回一个可观察的对象,每当查询语句查询的部分有变化时,都会回调它的onNext方法,直到Rx流断开。

3、Room与Flow联用

dao的方法需要改成这样:

@Query("select * from users where userId = :id")
fun getUserById(id: Long): Flow<UserEntity>
//直接拉取显示
private fun getUserListByRxJava() {
        lifecycleScope.launch {
            //flow
            AppDataBase.getInstance(this).userDao(). getUserById(1)
                .collect {
                    //获取数据UI 显示
                }
        }
    }

注意:只要数据库任意一个数据改变都会重新执行 query 操作并再次派发 Flow,这是因为 SQLite 数据库的内容更新通知功能是以表 (Table) 数据为单位,而不是以行 (Row) 数据为单位,因此只要是表中的数据有更新,它就触发内容更新通知。Room 不知道表中有更新的数据是哪一个,因此它会重新触发 DAO 中定义的 query 操作。

有关Android Jetpack架构组件(七)— Room的更多相关文章

  1. ruby - Ruby 和 Ruby on Rails 中的三层架构 - 2

    我是一名决定学习Ruby和RubyonRails的ASP.NETMVC开发人员。我已经有所了解并在RoR上创建了一个网站。在ASP.NETMVC上开发,我一直使用三层架构:数据层、业务层和UI(或表示)层。尝试在RubyonRails应用程序中使用这种方法,我发现没有关于它的信息(或者也许我只是找不到它?)。也许有人可以建议我如何在RubyonRails上创建或使用三层架构?附言我使用ruby​​1.9.3和RubyonRails3.2.3。 最佳答案 我建议在制作RoR应用程序时遵循RubyonRails(RoR)风格。Rails

  2. ruby-on-rails - 具有六边形架构和 DCI 模式的框架和数据库适配器 - 2

    我尝试用Ruby设计一个基于Web的应用程序。我开发了一个简单的核心应用程序,在没有框架和数据库的情况下在六边形架构中实现DCI范例。核心六边形中有小六边形和网络,数据库,日志等适配器。每个六边形都在没有数据库和框架的情况下自行运行。在这种方法中,我如何提供与数据库模型和实体类的关系作为独立于数据库的关系。我想在将来将框架从Rails更改为Sinatra或数据库。事实上,我如何在这个核心Hexagon中实现完全隔离的rails和mongodb的数据库适配器或框架适配器。有什么想法吗? 最佳答案 ROM呢?(Ruby对象映射器)。还有

  3. 设计一个亿级高并发系统架构 - 12306火车票核心场景DDD领域建模 - 2

    “架设一个亿级高并发系统,是多数程序员、架构师的工作目标。许多的技术从业人员甚至有时会降薪去寻找这样的机会。但并不是所有人都有机会主导,甚至参与这样一个系统。今天我们用12306火车票购票这样一个业务场景来做DDD领域建模。”开篇要实现软件设计、软件开发在一个统一的思想、统一的节奏下进行,就应该有一个轻量级的框架对开发过程与代码编写做一定的约束。虽然DDD是一个软件开发的方法,而不是具体的技术或框架,但拥有一个轻量级的框架仍然是必要的,为了开发一个支持DDD的框架,首先需要理解DDD的基本概念和核心的组件。一.什么是领域驱动设计(DDD)首先要知道DDD是一种开发理念,核心是维护一个反应领域概

  4. ruby - 写密集型特征的架构 - 2

    我在当前项目中使用由Oracle数据库和memcached支持的RubyonRails。有一个非常常用的功能,它依赖于单个数据库View作为数据源,并且该数据源内部有其他数据库View和表。这是一个虚拟数据库View,能够从一个地方访问所有内容,而不是物化数据库View。大多数情况下,如果用户正在使用他们希望更新的功能,那么让数据保持最新很重要。从这个View获取数据时,我将安全表内部连接到View(安全表不是View本身的一部分),其中包含一些我们用来在更细粒度级别上控制数据访问的字段。例如,安全表有user_id,prop_1,prop_2列,其中prop_1,prop_2是数据库

  5. Android Studio开发之使用内容组件Content获取通讯信息讲解及实战(附源码 包括添加手机联系人和发短信) - 2

    运行有问题或需要源码请点赞关注收藏后评论区留言一、利用ContentResolver读写联系人在实际开发中,普通App很少会开放数据接口给其他应用访问。内容组件能够派上用场的情况往往是App想要访问系统应用的通讯数据,比如查看联系人,短信,通话记录等等,以及对这些通讯数据及逆行增删改查。首先要给AndroidMaifest.xml中添加响应的权限配置 下面是往手机通讯录添加联系人信息的例子效果如下分成三个步骤先查出联系人的基本信息,然后查询联系人号码,再查询联系人邮箱代码 ContactAddActivity类packagecom.example.chapter07;importandroid

  6. ruby - 模块化、基于组件的 Sinatra 应用程序的架构 - 2

    我正在开发一个包含大约10个不同功能组件的Sinatra应用程序。我们希望能够将这些组件混合并匹配到应用程序的单独实例中,完全从config.yaml文件配置,如下所示:components:-route:'/chunky'component_type:FoodListercomponent_settings:food_type:baconmax_items:400-route:'places/paris'component_type:Mappercomponent_settings:latitude:48.85387273165654longitude:2.340087890625-

  7. 【哈士奇赠书活动 - 20期】-〖从程序员到架构师〗 - 2

    文章目录⭐️赠书活动-《从程序员到架构师》⭐️编辑推荐⭐️作者简介⭐️赠书活动→获奖名单⭐️赠书活动-《从程序员到架构师》内容简介:《从程序员到架构师:大数据量、缓存、高并发、微服务、多团队协同等核心场景实战》分为数据持久化层场景实战、缓存层场景实战、基于常见组件的微服务场景实战、微服务进阶场景实战和开发运维场景实战5个部分。基于对十余个架构搭建与改造项目的经验总结,介绍了大数据量、缓存、高并发、微服务、多团队协同等核心场景下的架构设计常见问题及其通用技术方案,包含冷热分离、查询分离、分表分库、秒杀架构、注册发现、熔断、限流、微服务等具体需求下的技术选型、技术原理、技术应用、技术要点等内容,将

  8. ruby - 如何使用( ruby ) Rack 中间件组件设置 cookie? - 2

    我正在为需要有条件地设置cookie的Rails应用编写Rack中间件组件。我目前正在尝试设置cookie。通过谷歌搜索,这似乎应该可行:classRackAppdefinitialize(app)@app=appenddefcall(env)@status,@headers,@response=@app.call(env)@response.set_cookie("foo",{:value=>"bar",:path=>"/",:expires=>Time.now+24*60*60})[@status,@headers,@response]endend它不会给出错误,但也不会设置coo

  9. Ruby 插件架构 - 2

    我想要一个非常基本的小型基础程序示例,它读入两个插件并注册它们。这两个插件以相同的方式以不冲突的方式挂接到基础程序。就此而言,我对任何编程语言的元编程都很陌生,我不确定从哪里开始。 最佳答案 我已经研究这个问题一段时间了。我尝试了很多不同的方法来做这件事,并征求了很多人的建议。我仍然不确定我所拥有的是否是“正确的方法”,但它运作良好并且很容易做到。在我的例子中,我专门查看配置并引入配置插件,但原理是相同的,即使我的术语特定于cnfiguration。在非常基础的层面上,我有一个配置类,里面什么都没有——它是空的。我还有一个Confi

  10. ruby-on-rails - 哪些组件使 VIM 成为一个好的(伟大的)ruby 编辑器? - 2

    我正在linux机器上学习rubyonrails并磨练我的VIM技能(skillz?)。当我在使用C++的时候开始使用VIM时,我有一个friend有一个很棒的vimfiles文件夹,里面有很多东西可以开始使用。从头开始,vim很棒,但感觉它还可以做得更好。我目前有:vim-rubybufferexplorerxml-edit(虽然我目前没有它可以处理erb文件)我知道这只是一些更有经验的vim/ruby开发人员所拥有的东西的皮毛(包括vim.rc文件中的一次性)。在某个地方是否有一个列表(或者我们可以创建一个)使ruby​​(和rails)编程更有趣所需的一堆标准vim配置?是否有一

随机推荐