Room 数据库框架最全攻略

Room 数据库框架最全攻略

RoomGoogle官方推出的Android Sqlite数据库处理框架,是子啊Sqlite上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。本文的目的旨在对于Room框架可以快速上手,内容分为两部分,第一部分为数据库的基本操作(增、删、改、查),第二部数据库的升级,加密

image

基本使用

基本工作的准备

  • 引入Room依赖 (在Module的build.gradle文件中添加如下依赖)
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'//在此处引入kapt插件
}

...

dependencies {
    ...
    
    def room_version = "2.2.6" // check latest version from docs
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    
    ...
}



以上准备工作就做的差不多了,接下来开始建立数据库表

  • 建表

    本文已 User 表为例

    @Entity
    data class User(
        @ColumnInfo(name = "first_name")
        val firstName: String,
        @ColumnInfo(name = "last_name")
        var lastName: String,
        @ColumnInfo(name = "age")
        val age: Int = 0,
         @PrimaryKey(autoGenerate = true)
        val uid: Int = 0
    )
    

    如上,一张User表就建好了,如果指定表名,默认是使用类名作为表明,如果要指定表名可以这样:

    @Entity(tableName = "first_user")
    data class User(
        @ColumnInfo(name = "first_name")
        val firstName: String,
        @ColumnInfo(name = "last_name")
        var lastName: String,
        @ColumnInfo(name = "age")
        var age: Int = 0
      @PrimaryKey(autoGenerate = true)
        val uid: Int = 0
    )
    

    在上面的代码中,一张first_user的表就建好了。里面有几个注解需要说明一些

    • @ColumnInfo(用来指定数据库表中对应的列明,属性 name 可以单独指定列名,如果不设置,默认为变量名作为列名)
    • @PrimaryKey (用来指定主键的,该注解修饰的字段属性必须为 int ,long等整型,属性autoGenerate 可以设置主键自增)

    在实际使用过中,以上写法其实是有问题的,上面代码指定了 uid做为主键,并且设置了自增,但是该属性放在构造方法的位置,书实例化User 这个对象的时候,这个 uid 的值是必须填写的。所以要不指定主键,让其自增,该如下实现

    @Entity(tableName = "first_user")
    data class User(
        @ColumnInfo(name = "first_name")
        val firstName: String,
        @ColumnInfo(name = "last_name")
        var lastName: String,
        @ColumnInfo(name = "age")
        var age: Int = 0
    ) {
        @PrimaryKey(autoGenerate = true)
        var uid: Int = 0
    
    }
    
  • Dao(包含用于访问数据库的方法)

    @Dao
    interface UserDao {
    }
    

    这个一个接口,包括了管理数据的方法,以 曾删改查为例

    @Dao
    interface UserDao {
         @Query("SELECT * FROM user")
        fun getUser(): MutableList<User>//查询所有
        
         @Delete
        fun delete(user: User)//删除指定的User
        
         @Update(entity = User::class)
        fun updateUser(user: User)//修改指定的User
        
         @Insert
        fun insertUser(vararg users: User)//插入数据,可以是单个,也可以是多个
    }
    
  • AppDatabase Dao管理对象

        @Database(entities = [User::class],version = 1)
        abstract class AppDatabase : RoomDatabase() {
            abstract fun userDao(): UserDao
            abstract fun carDao():CarDao
            ...
        }
        
    

    在以上代码中有几个注解简单说下

    • @Database Marks a class as a RoomDatabase. 标记一个类作为 RoomDatabase,被这个注解修饰类是个抽象类,并且需要继承 RoomDatabase,上面代码就是该类的固定写法
    • entities属性,该属性 修饰的是一个数组,该数组需要加入 被 @Entity 注解修饰的类,也就是数据库中的表
    • version 属性,该属性制动了数据库的版本
  • 创建数据库实例

    创建上述文件后,使用以下代码获取已创建的数据库的实例

        val db = Room.databaseBuilder(
                    applicationContext,
                    AppDatabase::class.java, "database-name"
                ).build()
        
    

    上述代码已经很清晰的展示了数据库对象的创建过程,“database-name”,该字符串指定了数据库的名称,可以按需求改成自己设置的名称

    注意:在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。每个RoomDatabase实例的成本相当高,如下


object DBHelper {
    val db = Room.databaseBuilder(
        App.context,
        AppDatabase::class.java,
        "snukaisens"
    ).build()
}
  • 测试

    
    class MainViewModel : ViewModel() {
        fun getUser(): MutableList<User> {
            return DBHelper.db.getUserDao().getUser()
        }
    
        fun insert(vararg users: User) {
            DBHelper.db.getUserDao().insertUser(*users)
        }
    
        fun findByName(firstName: String,lastName:String):User {
            return DBHelper.db.getUserDao().findByName(firstName, lastName)
        }
        fun updateUser(user: User) {
            DBHelper.db.getUserDao().updateUser(user)
        }
    }
    
    ...
    
    //需要说明的是,关于所有数据库相关的操作,都需要在子线程中执行
    GlobalScope.launch {
                val user = viewModel.getUser()//查询
                println("第一次取值 = $user")
    
                val insertUser = User("Tom","Json")
                val insertUser1 = User("Tom1","Json1")
                val insertUser2 = User("Tom2","Json2")
                val insertUser3 = User("Tom3","Json3")
                val insertUser4 = User("Tom4","Json4")
    
                                      viewModel.insert(insertUser,insertUser1,insertUser2,insertUser3,insertUser4)//批量插入
                val user1 = viewModel.getUser()//再次查询
                println(message = "第二次取值 = $user1")
    
                val onlyUser = viewModel.findByName("Tom4", "Json4")//按条件查询
                onlyUser.lastName = "sunjian"
                viewModel.updateUser(onlyUser)//修改
            }
    
    

    执行结果如下

    2021-03-12 15:16:18.211 30145-30269/com.example.firstapp I/System.out: 第一次取值 = []
    2021-03-12 15:16:18.213 30145-30269/com.example.firstapp I/System.out: 第二次取值 = [User(firstName=Tom, lastName=Json, age=0), User(firstName=Tom1, lastName=Json1, age=0), User(firstName=Tom2, lastName=Json2, age=0), User(firstName=Tom3, lastName=Json3, age=0), User(firstName=Tom4, lastName=Json4, age=0)]
    2021-03-12 15:16:18.215 30145-30269/com.example.firstapp I/System.out: 第三次取值 = [User(firstName=Tom, lastName=Json, age=0), User(firstName=Tom1, lastName=Json1, age=0), User(firstName=Tom2, lastName=Json2, age=0), User(firstName=Tom3, lastName=Json3, age=0), User(firstName=Tom4, lastName=sunjian, age=0)]
    

数据库升级

在开发过程中难免会碰到数据库表结构的改变,碰到这种情况,我们就需要对数据库表的结构进行升级,sqlite支持的数据库表结构的操作说明:

SQLite supports a limited subset of ALTER TABLE. The ALTER TABLE command in SQLite allows the user to rename a table or to add a new column to an existing table. It is not possible to rename a column, remove a column, or add or remove constraints from a table.

大致意思就是说,支持字段的添加和修改,不支持删除。所以针对数据库结构的升级就只是正对字段的添加和修改

  • 数据库表字段添加

我们正对之前Entity类进行操作,新增加一个e_mail字段

@Entity()
data class User(
    @ColumnInfo(name = "first_name")
    val firstName: String,
    @ColumnInfo(name = "last_name")
    var lastName: String,
    @ColumnInfo(name = "age")
    var age: Int = 0



) {
    @PrimaryKey(autoGenerate = true)
    var uid: Int = 0

    @ColumnInfo
    var email: String = ""//新增的数据库字段
}
  • 升级数据库的版本
@Database(entities = [User::class],version = 2)//版本号有原来的 1 -> 2
abstract class AppDatabase : RoomDatabase() {

    abstract fun getUserDao(): UserDao

}
  • 数据库对象中添加迁移配置
 val db = Room.databaseBuilder(
        App.context,
        AppDatabase::class.java,
        "snukaisens"
    ).addMigrations(Migration_1_2())
        .build()


//此处需要重点说明一下,添加字段之后,需要设置 NOT NULL属性,而且需要给默认值,要不然数据库迁移过程中就会提示,创建的数据库信息,和预期的不一致,从而导致闪退
 class Migration_1_2 : Migration(1,2){
        override fun migrate(database: SupportSQLiteDatabase) {
           database.execSQL("alter table User add column email TEXT NOT NULL DEFAULT ''")
        }
    }

数据库 User表中添加的email字段就成功了,接下来说一下,修改字段,还是以上面的带吗为例,添加字段之后,发现 email 这个字段好像不是很对,要修改成 e_mail,需要修改的内容如下:


//1.第一处需要修改的地方  修改字段
@Entity()
data class User(
    @ColumnInfo(name = "first_name")
    val firstName: String,
    @ColumnInfo(name = "last_name")
    var lastName: String,
    @ColumnInfo(name = "age")
    var age: Int = 0



) {
    @PrimaryKey(autoGenerate = true)
    var uid: Int = 0

    @ColumnInfo
    var e_mail: String = ""//字段重新修改
}

//2.修改版本号,记住这个版本
@Database(entities = [User::class],version = 2)
abstract class AppDatabase : RoomDatabase() {
    abstract fun getUserDao(): UserDao
}
//3.添加迁移配置
object DBHelper {
    val db = Room.databaseBuilder(
        App.context,
        AppDatabase::class.java,
        "snukaisens"
    ).addMigrations(Migration_1_2(),Migration_2_3())//注意此处需要传的参数是可变参数,直接添加就行了,不需要把之前的都删了
        .build()

    class Migration_1_2 : Migration(1,2){
        override fun migrate(database: SupportSQLiteDatabase) {
           database.execSQL("alter table User add column email TEXT NOT NULL DEFAULT ''")
        }

    }
    //修改字段 Migration 这个类构造中传的字段就是 数据库的升级前和升级后的版本号,一定要和 2 中的数字对应
    class Migration_2_3 : Migration(2,3){
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("alter table User rename column email to e_mail")
        }

    }
}

到此,数据库的修改就完成了。

数据库加密

在实际开发过程过程中,设计到安全问题,手机root过之后,用户可以随意拿到数据库文件,进行查看。针对一些敏感的数据库数据,需要对其进行加密。本文采用

库进行操作,操作很简单。

//引入依赖
implementation "net.zetetic:android-database-sqlcipher:4.4.2"
...

//添加配置
 private val factory = SupportFactory("xxxxxx".toByteArray())//此处xxxxxx 用户根据自己的情况自己配置
    val db = Room.databaseBuilder(
        App.context,
        AppDatabase::class.java,
        "snukaisens"
    ).addMigrations(Migration_1_2(), Migration_2_3())
        .openHelperFactory(factory)//添加factory
        .build()

这样就可以了,数据库加密就完成了,是不是很简单

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • AngularJS是什么?AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架。首先,它是...
    200813阅读 1,583评论 0 3
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,894评论 2 89
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,072评论 0 3
  • Room是一个对象关系映射(ORM)库。Room抽象了SQLite的使用,可以在充分利用SQLite的同时访...
    tuacy阅读 53,917评论 16 109
  • ### 什么是Vue.js + Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架(Reac...
    JLong阅读 1,043评论 0 0