【Android】Room —— SQLite的替代品

SQLite怎么就被Room给替代了?

SQLite为啥会被替代?

Sqlite从2000年问世至今,已有20多年历史,在这二十多年里,Sqlite已由原先的1.0版本升值目前的3.0版本,but,即使到了目前Android最新支持的2.3.0版本,它在Android上还是有一系列的缺点,如下:

  • 没有原始 SQL 查询的编译时验证。
  • 需要使用大量样板代码在 SQL 查询和数据对象之间进行转换。
  • 虽然这些 API 功能强大,但它们的级别相当低,需要大量时间和精力才能使用。

针对Sqlite一系列的问题,realm(适用于ios、Android)、ObjectBox(跨端,自称速度最快)、greenDao等本地数据库的目标,都是为了取代SQLite,甚至到后面,连官方都出了一款新的数据库 —— Room,这是要彻底的去Sqlite化?

答案是否定的,以上的数据库中,greenDAO Room都是在Sqlite进行修改、优化。ObjectBox是由greenDAO 的开发团队开发出来的产品,他们在greenDAO的Github首页,向大家推荐ObjectBox数据库,而greenDAO已有一年多未更新,也许,是greenDao的巅峰已经逝去,难以再次站上巅峰使得他们正慢慢的对它失去未来的期盼(个人猜想)。

那为啥,取代SQLite的是Room而不是greenDao呢?个人认为,这完全是因为官方的“官宣”导致的。在Android Developers官网找到SQLite的介绍页与使用指南,你会发现这样的字:

we highly recommended using the Room Persistence Library as an abstraction layer for accessing information in your app’s SQLite databases.
我们强烈建议使用 Room Persistence Library 作为抽象层来访问应用程序的 SQLite 数据库中的信息。

在这里插入图片描述
放弃SQLite,劝说我们使用Room的原因就是SQLite的缺点与Room的优点。

Room优点:

  • 简化的数据库迁移路径。
  • SQL 查询的编译时验证。
  • 方便的注释,最大限度地减少重复和容易出错的样板代码。

集成Room

Room 包含三个主要组件,分别是:

  • Database:保存数据库并用作与应用程序持久数据的底层连接的主要访问点的数据库类。
  • Entity:表示应用数据库中表的数据实体。
  • Dao:数据访问对象,提供您的应用可用于在数据库中查询、更新、插入和删除数据的方法。

导入依赖

implementation("androidx.room:room-runtime:2.4.2")
annotationProcessor("androidx.room:room-compiler:2.4.2")

// 使用 Kotlin 注释处理工具 (kapt)
kapt("androidx.room:room-compiler:2.4.2")

注意:Kotlin 插件支持注解处理器,如 Dagger 或 DBFlow 。 为了让它们与 Kotlin 类一起工作,使用kapt需要应用kotlin-kapt插件。

apply plugin: 'kotlin-kapt'

点击前往查看最新版本依赖

Room的使用有三个步骤,分别是Database、Entity、Dao,分别创建完成后才能对数据进行写入、修改等操作。

Entity(数据实体)

在 Room 中,可以定义实体来表示想要存储的对象。每个实体对应关联的 Room 数据库中的一个表,一个实体的每个实例代表对应表中的一行数据。

Kotlin 版本的 Entity 与 Java 版本的 Entity 写法不一,Java 版本的 Entity 写法正常是新建一个实体类并实现变量的get、set方法,Kotlin 版本的 Entity 使用了data class关键字,在 Kotlin 中,使用data class标记的类为数据类,该类会自动重写 Kotlin 的超类Anyequals(other: Any?)hashCode()toString()方法,更详细的说法请移步至→Kotlin 数据类(Data)

data class声明为 Room 的数据类,需使用@Entity注解声明,默认情况下,Room 会使用类名作为数据库表名,若不希望将类名与数据库表名相同,可在@Entity注解设置tableName参数,如:@Entity(tableName = "自定义的数据库表名"),添加参数后,此时该类生成的数据库表名将不再是类名。

@Entity
data class Classes(
    @PrimaryKey val classesId: String,
    @ColumnInfo val classesName: String
)

其它注解

Annotation Describe
@PrimaryKey 用于唯一标识相应数据库表中的每一行
@ColumnInfo 定义数据表的列名
@Ignore 不将字段添加到数据表里
@NonNull 字段或者方法的返回值不可以为null

@Entity详细使用方式见:使用 Room 实体定义数据

Dao(数据访问对象)

Dao:提供应用程序的其余部分用来与数据表中的数据交互的方法。

Dao是一个接口类,不需要写方法体,可直接使用注解代替,增删改查分别对应以下四个注解:

Annotation Describe
@Insert
@Delete
@Update
@Query

注解使用方式如下:

@Dao
interface ClassesDao {
    
    
    /**
     * 增加多个班级
     */
    @Insert
    fun addMultipleClasses(vararg classes: Classes)
	/**
     * 删除某条数据
     */
    @Delete
    fun deleteClasses(classes: Classes)
    /**
     * 修改某条数据
     */
    @Update
    fun updateClasses(classes: Classes)
    /**
     * 查询classes表的数据
     */
    @Query("select * from classes")
    fun getAllClasses() : List<Classes>
}

在 Room 中,带值查询使用的是冒号表示,如下代码,表示查找的 className 等于调用该接口时传的值。

@Query("select * from classes where className = :classesName")
fun getAllClasses(classesName: String) : List<Classes>

Dao详细使用方式见:使用 Room DAO 访问数据

Database(数据库)

Entity、Dao都已经定义了,最后一步就是创建数据库类来保存数据,创建的类必须满足数据库的三个条件:

  • 该类必须是扩展RoomDatabase类的抽象类 。
  • 对于与数据库关联的每个 Dao 类,数据库类必须定义一个具有零参数并返回 Dao 类实例的抽象方法。
  • 该类必须使用@Database包含一个entities数组的注释进行注释,该数组列出了与数据库关联的所有数据实体(包括数据库版本)。
@Database(entities = [Classes::class/*数据库包含的实体*/], version = 1, exportSchema = false)
abstract class SchoolDatabase : RoomDatabase() {
    
    
    /**
     * 调用该方法获取Dao,来调用Classes表的接口方法
     */
    abstract fun classDao(): ClassesDao
}

使用

Room.databaseBuilder(applicationContext, SchoolDatabase::class.java, "数据库名称")
     .build()
     .classDao()
     .addClasses(classes)

效果

在这里插入图片描述

数据迁移

为何数据库要做数据迁移?

大家可能在开发本地数据库过程中都会遇到这样的一个问题,对数据库的字段进行修改后,重新启动会出现闪退。

有的同学遇上这个问题可能会使用这样的关键字去搜索:数据库字段修改闪退的解决办法。搜索出来的结果就是会叫你去卸载app重新安装就好,问题即可得以解决。但是,这个看似正确的方法其实是致命的。

本地数据库修改一次,打包发布,用户更新app后如果使用到本地的数据库,会报Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.的异常错误。

在这里插入图片描述
一个app的界面如果在8秒内没有加载完成,就会流失70%用户,8秒定律如此,那闪退一次会流失多少用户呢?解决上述问题,只能对数据库进行迁移,而数据迁移,又分为自动迁移与手动迁移。

自动迁移

自动迁移提供了四种注解用于自动迁移,分别是:

使用方式如下:

// 版本更新前的数据库类.
@Database(
  version = 1,
  entities = [User::class]
)
abstract class AppDatabase : RoomDatabase() {
    
    
  ...
}

// 版本更新后的数据库类.
@Database(
  version = 2,
  entities = [User::class],
  exportSchema = true,
  autoMigrations = [
    AutoMigration (from = 1, to = 2)
  ]
)
abstract class AppDatabase : RoomDatabase() {
    
    
  ...
}

由于目前官方仅仅提供以上四个注解用于自动迁移,添加字段如何操作就成了一个迷,如果你在观看该文章的时候了解到了自动迁移新增的数据表字段,希望你能在评论区一起分享。

注意:
1、 使用自动迁移exportSchema必须设置为true,避免迁移失败。
2、自动迁移仅仅适用于 2.4.0-alpha01 及更高版本,低版本、涉及复杂架构更改的情况必须使用手动迁移。

参考文章:Room 数据库迁移

手动迁移

数据库手动迁移第一步,先修改数据表结构。

@Entity
data class Classes(
    @PrimaryKey(autoGenerate = true) val classesId: Int = 0,
    @ColumnInfo(name = "className") var classesName: String,
    // 新增的列
    @ColumnInfo(name = "classesNum") var classesNum: String
)

手动迁移使用到RoomDatabase.BuilderaddMigrations(@NonNull Migration... migrations)方法,将新增的Migration类放进去即可完成迁移。

Room 的底层使用的是 SQLite ,数据迁移使用的就是SQLite 的 execSQL(String sql)方法执行 sql 语句对数据库进行修改,以此来完成迁移,代码如下:

var db: SchoolDatabase ?= null
// 单例模式的双重检查锁
fun getDataBase(context: Context) : SchoolDatabase{
    
    
    if (db == null){
    
    
        synchronized(SchoolDatabase::class.java){
    
    
            if (db == null){
    
    
                db = Room.databaseBuilder(context.applicationContext, SchoolDatabase::class.java, "school")
                    // .fallbackToDestructiveMigration()
                    // 添加数据迁移
                    .addMigrations(MIGRATION_1_TO_2)
                    .build()
            }
        }
    }
    return db as SchoolDatabase;
}

// Migration实例的两个参数指的是当前版本升级到什么版本
val MIGRATION_1_TO_2 : Migration = object : Migration(1, 2) {
    
    
    override fun migrate(database: SupportSQLiteDatabase) {
    
    
        //  添加新的列使用 底层SQLite + sql 语法,其它增删改查亦是如此
        database.execSQL("alter table classesNum add column 新增的列名 列名的数据类型 default 'null'")
    }
}

最后不要忘了在数据库类名上的@Database注解的version参数改为最新版本,再次 run 即可完成迁移。

参考:Android开发基础教程

结合RxJava使用

Room 执行数据库操作,默认使用的是子线程去执行,如果需要在主线程去执行的可调用Room.databaseBuilderallowMainThreadQueries()来禁用 Room 的主线程查询检查,但不建议这样操作,这样会加大触发 ANR 的概率。

如果每次执行 Room 操作都新建一个Thread并实现Runnable接口,这样不更加容易触发 ANR 吗?

为此,官方推出了一个与 Rxjava 相结合使用的库,方便线程间的切换。

// optional - RxJava3 support for Room
implementation("androidx.room:room-rxjava3:2.4.2")
// RxJava3
implementation ("io.reactivex.rxjava3:rxjava:3.1.3")
implementation ("io.reactivex.rxjava3:rxandroid:3.0.0")

具体结合 RxJava 的操作可参考文章:
1、Room数据库实战:搭配RxJava使用与封装
2、Room配合RxJava2,使用方法兼使用心得兼注意事项


本文代码:【Android】Room —— SQLite的替代品Demo

参考文档
1、Android Room 代码示例
2、Android Developers —— Room
3、Android Developers —— Room 使用指南
4、Android Developers —— Defining data using Room entities

猜你喜欢

转载自blog.csdn.net/baidu_41616022/article/details/125424197