Android Jetpack 架构组件之 Room

一、Room简介

Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。该库可帮助您在运行应用的设备上创建应用数据的缓存。此缓存充当应用的单一可信来源,使用户能够在应用中查看关键信息的一致副本,无论用户是否具有互联网连接。

二、依赖

def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"

如果你是使用Java语言开发,同时还要添加下面这项依赖:

 annotationProcessor "androidx.room:room-compiler:$room_version" 

如果是 使用Kotlin语言开发,那么则是将上面Java环境下的依赖换成下面这项:

 kapt "androidx.room:room-compiler:$room_version" 

并同时添加下面这个插件:

apply plugin: 'kotlin-kapt'

上面加粗的部分依赖一定要注意,否则恭喜入坑。

三、Room库的三个基础但是很重要的概念:

  1. Entity
    实体类,也就是数据库的一张表结构。使用注解@Entity标记。
  2. Dao
    包含一系列访问数据库的方法。使用注解@Dao标记。
  3. Database
    数据库持有者,是与应用持久化相关数据的底层连接的主要接入点。使用注解@Database标记。

使用**@Database注解需满足三个条件**:
1、定义的类必须是继承于RoomDatabase的抽象类。
2、在注解中需要定义与数据库相关联的实体类列表。
3、包含一个没有参数的抽象方法并且返回一个带有注解的 @Dao。

四、几个注解

Entity:

使用@Entity注解定义的类会被映射为数据库中的一张表。默认表名为实体类的类名,字段名为表名,可以修改。

@Entity(tableName = "JinGuangBuDaiXi")
data class Role(
    /**
     * roleId字段
     * 必须
     * 指定,系统会自动生产这个字段的值。请注意上面的<!-----“必须”----->二字
     * */
    @PrimaryKey(autoGenerate = true) var roleId: Long,
    @ColumnInfo(name = "userName") var name: String,
    @ColumnInfo(name = "SHIHAO", defaultValue = "jinguangbudaixi") var shihao: String,
    @ColumnInfo(name = "kongfu") var wugong: String
) {
// 实际使用中,上面的方法里面,roleId字段必须指定,添加了Ignore的字段也必须指定,但是按理说这两个都不应该必须指定的。
// roleId是id,每次添加的时候自增1,不指定的话,按其默认的规则来。但实际使用的时候,必须指定ID。
// Ignore:既然都说可以忽略了,那么我没有指定你系统忽略就是了,为什么还必须要指定?
}

上面我们看到,又出现了几个新的注解:@Entity、@PrimaryKey、@ColumnInfo和@Ignore,它们的含义如下:

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

几种注解的源码如下:

Entity注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Entity {
    String tableName() default "";
    Index[] indices() default {};
    boolean inheritSuperIndices() default false;
    String[] primaryKeys() default {};
    ForeignKey[] foreignKeys() default {};
    String[] ignoredColumns() default {};
}

PrimaryKey注解:

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface PrimaryKey {
    boolean autoGenerate() default false;
}

ColumnInfo注解:

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface ColumnInfo {
    String name() default INHERIT_FIELD_NAME;
    @SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;
    boolean index() default false;
    @Collate int collate() default UNSPECIFIED;
    String defaultValue() default VALUE_UNSPECIFIED;
    String INHERIT_FIELD_NAME = "[field-name]";
    int UNDEFINED = 1;
    int TEXT = 2;
    int INTEGER = 3;
    int REAL = 4;
    int BLOB = 5;
    @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
    @Retention(RetentionPolicy.CLASS)
    @interface SQLiteTypeAffinity {
    
    }
    int UNSPECIFIED = 1;
    int BINARY = 2;
    int NOCASE = 3;
    int RTRIM = 4;
    @RequiresApi(21)
    int LOCALIZED = 5;
    @RequiresApi(21)
    int UNICODE = 6;
    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})
    @Retention(RetentionPolicy.CLASS)
    @interface Collate {
}

Ignore注解:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface Ignore {

}
Dao

Dao类是一个接口interface,其中定义了一系列的操作数据库的方法。通常我们操作数据库无非就是增、删、改、查。Room也为我们的提供了相关的注解,有@Insert、@Delete、@Update 和 @Query

@query:查询注解,参数是String类型,我们直接写SQL语句进行执行,而且编译的时候可以进行语法检查。

@Query("select * from role where userId = :id")
fun getRoleById(id: Long): Role

SQL语句引用传递的参数直接使用 :符号进行引用。
上面Query后面的sql语句中,user是指我们所建的实体类;userId则是我们创建实体类时指定的那个Long型数据的Id。我们还可以这么改:

@Query("select * from Role where name = :mName")
fun getRoleById(mName: String): Role

@Insert:如果我们需要向表中插入一条数据,我们直接定义一个方法并用 @Insert注解标注就可以:

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addRole (mRole: Role)

我们看到直接中有个参数onConflict,表示当插入的数据已经存在时候的处理逻辑,有三种操作逻辑:REPLACEABORTIGNORE。如果不指定则默认为ABORT终止插入数据。这里我们将其指定为REPLACE替换原有数据。
@Delete:如果需要删除表的数据则使用 @Delete注解:

@Delete 
fun deleteRoleByRole(mRole: Role): 使用主键来查找要删除的实体。

@Update: 如果需要修改某一条数据则使用 @Update注解,和@Delete一样也是根据主键来查找要删除的实体。

@Update
fun updateRoleByRole (mRole: Role)

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

@Query("delete from role where userId = :id ") 
fun deleteRoleById(id:Long)

@Query("update role set userName = :updateName where userID =  :id") 
fun update(id: Long, updateName: String)

最终Dao的结构如下:

@Dao
interface RoleDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
	fun addUser(mRole:Role)

    //Insert 可以单独插入,也可以批量插入
    @Insert
	fun insertUser(mRole:Role)

    @Insert
	fun insertRoles(mRole:List<Role>)

    //Query的值即使要执行的sql语句
    @Query("SELECT * FROM JinGuangBuDaiXi")
	fun getAll():List<Role>

    @Query("SELECT * FROM JinGuangBuDaiXi WHERE userName in (:names)")
	fun getRolesByMames(names:List<String>):List<Role>

    //注意点:1、在Room使用中,模糊匹配的格式为" like '%' || :userName || '%' ",即在要匹配的参数值前后要加上 “||”,并且“%”要区分出来
    //       2、下面查询用到的变量的名字要跟实体类(比如本demo中的Role.kt)中的变量要保持一致,否则查询失败
    //       3、WHERE后面的变量(比如本例中的“userName”)要用实体类(比如本demo中的Role.kt)中注解后面的“name”字段的值,否则查询失败。
    @Query("SELECT * FROM JinGuangBuDaiXi WHERE userName like '%'|| :name ||'%' LIMIT 1")
	fun getRoleInfoByName(name:String):Role

    @Query("SELECT * FROM JinGuangBuDaiXi WHERE kongfu like '%'|| :wugong ||'%' LIMIT 1")
	fun getRoleInfoBykONGfU(wugong:String):Role

    @Query("SELECT * FROM JinGuangBuDaiXi WHERE SHIHAO like '%'|| :shihao ||'%' LIMIT 1")
	fun getRoleInfoByShiHao(shihao:String):Role

    @Update
	fun updateRoleByRole(user: Role)

    @Delete
    fun deleteRoleByRole(mRole: Role)
}
Database

首先定义一个抽象类继承RoomDatabase类,并添加注解 @Database 来标识:

@Database(version = 1, entities = [Role::class])
abstract class RoleDatabase : RoomDatabase() {
    abstract fun roleDao():RoleDao
    companion object {
        private var instance: RoleDatabase? = null
        fun getInstance(context: Context): RoleDatabase {
            if (instance == null) {
                instance = Room.databaseBuilder(
                    context, RoleDatabase::class.java, "Role.db" ).allowMainThreadQueries().build()//"Role.db" :数据库名称
            }
            return instance as RoleDatabase
        }
    }
}

使用entities来映射相关的实体类,version来指明当前数据库的版本号。这里使用了单例模式来返回Database,以防止新建过多的实例造成内存的浪费。

Room.databaseBuilder(context,klass,name).build()来创建Database,其中第一个参数为上下文,第二个参数为当前Database的class字节码文件,第三个参数为数据库名称。

默认情况下Database是不可以在主线程中进行调用的。因为大部分情况,操作数据库都还算是比较耗时的动作。如果需要在主线程调用则使用allowMainThreadQueries进行说明。

具体使用:

几个关键的步骤都完成后,接下来我们自然是开始进入使用阶段:

class RoomSqlActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_room_sql)

        var roleDao = RoleDatabase.getInstance(this).roleDao()
        var name = "任缥缈"
        var shihao = "风满楼,卷黄沙,舞剑春秋,名震天下。雨飘渺,倦红尘,还君明珠,秋水浮萍。"
        var wugong = "飘渺剑法:虚无飘渺,无迹可寻,精妙绝伦,变幻莫测。无形无相"
        btnAdd.setOnClickListener {
            var role01 = Role(1L, name, shihao, wugong)
            roleDao.addUser(role01)
            name = "俏如来"
            shihao = "红尘轮回众生顾,因果循环有定数,放下屠刀虽成佛,愿坠三途灭千魔。"
            wugong =
                "圣印六式;诛魔之利:止戈流·「鬼破、星流、星陨、星痕、月痕、日殒、破日、天誓」;止戈流·真阵·「初式·十剑山河荡狼烟、继式·十面江海靖尘嚣、终式·十万沙劫漫云天」"
            var role02 = Role(2L, name, shihao, wugong)
            roleDao.addUser(role02)

            name = "藏镜人"
            shihao = "踏烽火,折兵锋,正邪无用;斩敌颅,杀魍魉,天地不容"
            wugong = "以纯阳金刚体强行逆练纯阴功法:飞瀑掌;纯阳掌;纯阳行左·飞瀑走右·阴行阳招·阳行阴招·阴阳融合·袭地贯天;啸问岁月·天狼影"
            var role03 = Role(3L, name, shihao, wugong)
            roleDao.addUser(role03)

            name = "遥星公子别小楼"
            shihao = "沉刀埋霜小楼庭,回首江湖风云轻。君有才能纵捭阖,清溪仰望有遥星"
            wugong =
                "纵横诀·『吴钩霜月明、飒沓如流星、千里不留行、十步杀一人、赵客缦胡缨、银鞍照白马;星月合招:十方萧索无涯·千古夕阳有主·诗仙纵横·刀剑茫茫去不还』、碧海定涛掌"
            var role04 = Role(4L, name, shihao, wugong)
            roleDao.addUser(role04)

            println("TAGTAG:执行了添加操作")
        }

        btnQuery.setOnClickListener {
            roleDao.getAll().forEach() {
                println("TAGTAG:执行了查询操作:${it.toString()}")
            }
        }
        btnInsert.setOnClickListener {
            name = "宫本总司"
            shihao = "萧无名、曲无名、声幽幽、声悲鸣,心何闷?情何困?眉深锁、孤独行。"
            wugong = "无常无定。神魔一念:以魔入心,以神出招,心法极意,不在魔心控杀,而在神意止杀,忘情忘仇,亦神亦魔,神魔非我。一剑无极、一剑无尽、一剑无声、一剑无悔"
            var roleInsert = Role(5L, name, shihao, wugong)
            roleDao.insertUser(roleInsert)
            println("TAGTAG:执行了  插入  操作")
        }

        btnQuery.setOnClickListener {
            name = "任缥缈"
            var role = roleDao.getRoleInfoByName(name);
            println("TAGTAG:通过${name}查询到的信息:${role?.toString()}")

            wugong = "飘渺剑法:虚无飘渺,无迹可寻,精妙绝伦,变幻莫测。无形无相"
            role = roleDao.getRoleInfoBykONGfU(wugong)
            println("TAGTAG:通过${wugong}查询到的信息:${role?.toString()}")

            shihao = "萧无名、曲无名、声幽幽、声悲鸣,心何闷?情何困?眉深锁、孤独行。"
            role = roleDao.getRoleInfoByShiHao(shihao)
            println("TAGTAG:通过${shihao}查询到的信息:${role?.toString()}")

            var nameList = listOf<String>("任缥缈", "俏如来")
            var nameQueryList = roleDao.getRolesByMames(nameList)
            for (item in nameQueryList){
                println("通过namelist查询到的结果:${item.toString()}")
            }
        }

        btnUpdate.setOnClickListener {
            name = "俏如来"
            shihao = "天堂地狱一道门,道门无扉三朵云。云中难觅五形气,气化心逢七彩君。"
            wugong = "圣印六式;诛魔之利:止戈流·「鬼破、星流、星陨、星痕、月痕、日殒、破日、天誓」;止戈流·真阵·「初式·十剑山河荡狼烟、继式·十面江海靖尘嚣、终式·十万沙劫漫云天」"
            var roleUpdate = Role(2L, name, shihao, wugong)
            roleDao.updateRoleByRole(roleUpdate)

            roleUpdate = roleDao.getRoleInfoByName(name)
            println("TAGTAG:更新后的结果:${roleUpdate.toString()}")
        }

         btnDelete.setOnClickListener {
             name = "俏如来"
             shihao = "天堂地狱一道门,道门无扉三朵云。云中难觅五形气,气化心逢七彩君。"
             wugong = "圣印六式;诛魔之利:止戈流·「鬼破、星流、星陨、星痕、月痕、日殒、破日、天誓」;止戈流·真阵·「初式·十剑山河荡狼烟、继式·十面江海靖尘嚣、终式·十万沙劫漫云天」"
             var roleUpdate = Role(2L, name, shihao, wugong)
             roleDao.deleteRoleByRole(roleUpdate)

             var list = roleDao.getAll()
             for (item in list){
                 println("TAGTAG:删除指定数据:俏如来后查询到的结果:${item.toString()}")
             }
         }
    }
}

运行结果:
在这里插入图片描述

数据库的升级和降级:

在使用数据库的时候避免不了的就是数据库的更新。数据库的升级或者降级使用addMigrations方法进行操作, 然后完整的Database应该是这样的:

@Database(version = 1, entities = [Role::class])
abstract class RoleDatabase : RoomDatabase() {
    abstract fun roleDao():RoleDao
    companion object {
        private var instance: RoleDatabase? = null
        /**
         *startVersion:表示的是升级开始的版本
         * endVersion:表示要升级到的目标版本,endVersion>startVersion
         */
        fun getInstance(context: Context, isUpdateVersion:Boolean, startVersion:Int, endVersion:Int): RoleDatabase {
            if (instance == null) {
                if (isUpdateVersion){ //"Role.db" :数据库名称
                    instance = Room.databaseBuilder(context,RoleDatabase::class.java,"Role.db" ).allowMainThreadQueries().build()
                }else{
                    /**
                     * startVersion表示的是升级开始的版本,
                     * endVersion表示要升级到的目标版本,endVersion>startVersion。
                     * 同时需要将@Database注解中的version的值修改为和endVersion相同。
                     * 数据库的升级或者降级使用addMigrations方法进行操作
                     */
                    instance = Room.databaseBuilder(context,RoleDatabase::class.java,"Role.db"
                    ).addMigrations(object : Migration(startVersion,endVersion){
                        override fun migrate(database: SupportSQLiteDatabase) {
                            database.execSQL("ALTER TABLE user ADD age INTEGER Default 0 not null ")
                        }
                    }).allowMainThreadQueries().build()
                }
            }
            return instance as RoleDatabase
        }
    }
}

Migration方法有两个参数:

startVersion:表示的是升级开始的版本。
endVersion:表示要升级到的目标版本,endVersion>startVersion。同时需要将@Database注解中的version的值修改为和endVersion相同。

当在升级或者降级的过程中出现版本未匹配到的情况的时候,默认情况下会直接抛异常出来。当然我们也可以处理异常。

升级的时候可以添加fallbackToDestructiveMigration方法,当未匹配到版本的时候就会直接删除表然后重新创建。

降级的时候添加fallbackToDestructiveMigrationOnDowngrade方法,当未匹配到版本的时候就会直接删除表然后重新创建。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值