美文网首页
Room 数据库框架最全攻略

Room 数据库框架最全攻略

作者: sunjiandev | 来源:发表于2021-03-12 16:57 被阅读0次

    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()
    

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

    相关文章

      网友评论

          本文标题:Room 数据库框架最全攻略

          本文链接:https://www.haomeiwen.com/subject/hrmzqltx.html