美文网首页
Room数据库框架使用

Room数据库框架使用

作者: 静水红阳 | 来源:发表于2020-11-13 22:23 被阅读0次

    概述

    room是Google官方推荐的一个数据库Sqlite框架。Room在SQLite上提供了一个抽象层,以便在充分利用Sqlite的功能的同时也能够访问数据库。

    依赖引入

    首先需要在gradle中引入room依赖包,示例如下:

    dependencies {
      //版本
      def room_version = "2.2.5"
    
      implementation "androidx.room:room-runtime:$room_version"
      kapt "androidx.room:room-compiler:$room_version"
    
      // optional - Kotlin Extensions and Coroutines support for Room
      implementation "androidx.room:room-ktx:$room_version"
    
      // optional - Test helpers
      testImplementation "androidx.room:room-testing:$room_version"
      //optional   - rxjava for room 
      implementation "androidx.room:room-rxjava2:$room_version"
    }
    

    Room结构

    Room主要包含有三个组件:DataBase(数据库),Entity(数据表结构),DAO(数据表操作)。

    常规的操作流程如下:

    1. APP使用DataBase来获取与该数据关联的数据表操作对象:DAO。
    2. 应用使用每个DAO从数据库中获取实体:Entity。
    3. 应用对Entity进行更改或获取数据。
    4. 应用将对Entity的更改通过DAO保存到数据库中。

    具体的结构如下图所示:


    room框架.png

    DataBase

    Database主要是包含了DAO并且提供创建和链接数据库的方法。

    1. 创建数据库

    创建DataBase主要包括如下几个步骤:

    1. 创建继承RoomDatabase的抽象类
    2. 在继承类前使用注解@Database
    3. 声明数据库结构的Entity并设置数据库版本号。
    4. 数据库实例最好能够定义为单例。
      示例代码如下:
    @Database(entities = [TestUserEntity::class], version = 2, exportSchema = false)
    abstract class TestDataBase : RoomDatabase() {
    
        //获取DAO数据库操作
        abstract fun getDao(): ITestUserDao
    
        //单例模式
        companion object {e
            private const val DB_NAME = "test_user_database"
    
            @Volatile
            private var INSTANCE: TestDataBase? = null
    
            fun getDatabase(context: Context): TestDataBase {
                val tempInstance = INSTANCE
                if (tempInstance != null) {
                    return tempInstance
                }
                synchronized(this) {
                    val instance = Room.databaseBuilder(
                        context.applicationContext,
                        TestDataBase::class.java, DB_NAME
                    ).build()
                    INSTANCE = instance
                    return instance
                }
            }
    
            //升级语句
            private val MIGRATION_1_2 = object : Migration(1, 2) {
                override fun migrate(database: SupportSQLiteDatabase) {
    //                database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
                }
            }
        }
    }
    
    数据库迁移

    当升级Android应用时,有时需要更改数据库中的数据结构,要用户升级应用的时候保持原有的数据不变。此时就需要用到数据迁移Migration。

    Room中通过支持Migration类进行增量迁移以满足此需求。通过设置不同的Migration对象完成版本升级中的变动。当应用更新需要升级数据库版本时,Room会从一个或者多个Migration对象中运行migrate()方法,实现数据库版本升级。

    val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " +
                    "PRIMARY KEY(`id`))")
        }
    }
    
    val MIGRATION_2_3 = object : Migration(2, 3) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
        }
    }
    
    Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
            .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
    
    注意事项

    数据迁移必须完整的定义所有版本的迁移,否则如果没有对应版本的Migration时,Room数据库会清楚原来的数据重写创建。

    DAO

    DAO全称:data access object,可以通过SQL语句进行对数据库的操作并且将这些语句和JAVA中的方法关联调用,编译器会检查SQL语句并且通过注解生成对应的SQL语句及数据库操作。

    在Room中,需要被设置为DAO需要注意如下几点:

    1. DAO必须是抽象类或者接口。
    2. DAO必须要使用@Dao注解进行标识。

    DAO示例代码如下:

    @Dao
    interface ITestUserDao {
        //采用suspend关键字使用kotlin协程使得数据库的操作方法不在主线程进行
        @Query("select * from test_user")
        fun getAll(): List<TestUserEntity>
    
        @Query("select * from test_user where id = (:userId)")
        fun getById(userId: Long): Flowable<TestUserEntity>
    
        @Update
        suspend fun updateUserInfo(vararg userEntity: TestUserEntity)
    
        @Insert
        fun insertUser(vararg userEntity: TestUserEntity)
    
        @Delete
        fun deleteUser(vararg userEntity: TestUserEntity)
    
        @Query("delete from test_user where id = (:userId)")
        fun deleteById(userId: Long)
    
    }
    

    新建一个DAO主要SQL语句的设置,可以从如下几个方面进行设置:

    1. 插入数据
    1. 插入数据方法需要在方法前使用@Insert注解进行标识。
    2. 当参数只有一个时,则它可以返回 long,这是插入项的新 rowId。
    3. 如果参数是数组或集合,则应返回 long[] 或 List<Long>。
    4. 查看编译文件,可以发现插入操作是在一个独立的事务中完成的,保证了插入操作的原子性。
    @Dao
        interface MyDao {
            @Insert(onConflict = OnConflictStrategy.REPLACE)
            fun insertUsers(vararg users: User)
    
            @Insert
            fun insertBothUsers(user1: User, user2: User)
    
            @Insert
            fun insertUsersAndFriends(user: User, friends: List<User>)
        }
    
    2. Update
    1. Update方法需要采用注解@Update来完成
    2. Update方法使用和每个实体的主键匹配查询
    3. Update方法也可以返回Int值,表示数据库中更新的行数。
        @Dao
        interface MyDao {
            @Update
            fun updateUsers(vararg users: User)
        }
    
    3. 删除
    1. Delete方法需要采用注解@Delete进行标识
    2. Delete方法使用的是主键查询要删除的实体。
    3. Delete方法也可以返回Int值,表示数据库中更新的行数。
    @Dao
        interface MyDao {
            @Delete
            fun deleteUsers(vararg users: User)
        }
    
    4. 查询
    1. 查询方法需要使用@Query注解进行标识
    2. @Query不仅仅可以用来标识查询方法,还可以标识其他的任意SQL语句
    3. 每个@Query方法在编译时就是检查SQL语句是否正确,如果存在错误则直接编译报错,而不是运行时失败。
    @Dao
        interface MyDao {
            @Query("SELECT * FROM user WHERE age > :minAge")
            fun loadAllUsersOlderThan(minAge: Int): Array<User>
        }
    
    5. 带参数查询
    1. 在查询过程通添加条件不可避免的需要添加参数,可以在SQL语句中使用:param的方式引入参数。
    2. 如果编译检查SQL语句没有找打匹配的参数,则会编译报错。
    @Dao
        interface MyDao {
            @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
            fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>
    
            @Query("SELECT * FROM user WHERE first_name LIKE :search " +
                   "OR last_name LIKE :search")
            fun findUserWithName(search: String): List<User>
        }
    
    6. 传递参数的集合
    1. 存在有部分查询传入的数量不定的参数,参数的确切数量要到运行时才能得到,可以采用如下的参数合集的方式进行完成。
    @Dao
        interface MyDao {
            @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
            fun loadUsersFromRegions(regions: List<String>): List<NameTuple>
        }
    
    7. 查询语句返回对象类型
    1. 如果需要返回实体类的部分字段,可以通过新建一个只包含这部分字段的类,返回返回此新类的集合对象。
    2. 查询语句还支持分布其他的返回类型,例如RxJava中的Flowable<T>等。
    3. DAO里的方法不能够放在主线程执行,因此需要等待
    4. 可以对DAO里的方法添加suspend关键字,以使用kotlin协程功能使得这个方法成为异步方法
    5. LiveData:如果返回为LiveData对象,则不需要进行异步操作,因为room会自动完成异步操作。
    数据库查询类型.png
    8. 事务
    1. 实现自定义事务需要添加如下注解@Transaction进行标识。
    @Dao
        abstract class UsersDao {
            @Transaction
            open suspend fun setLoggedInUser(loggedInUser: User) {
                deleteUser(loggedInUser)
                insertUser(loggedInUser)
            }
    
            @Query("DELETE FROM users")
            abstract fun deleteUser(user: User)
    
            @Insert
            abstract suspend fun insertUser(user: User)
        }
    

    Entity

    在Room DataBase中,Entity表示的是一张数据表的结构。举个例子来说,我们新建一个UserEntity类。

    @Entity(tableName = "test_user")
    class TestUserEntity(
        @PrimaryKey
        @ColumnInfo(name = "id") 
        var userId: Long,
        
        @ColumnInfo(name = "name")
        var name: String? = null
        ) {}
    

    如上述代码所示,一个简单的user Entity类就定义好了,同时也定义了一个名为test_user的数据表,其中的列信息主要有id和name两项,Entity对象和数据表的对应关系如下:

    1. 一个Entity对象代表数据表中的一行
    2. Entity类对应一个数据表,其成员变量对应数据表中的列

    新建一个Entity可以从如下几个方面进行设置

    1. 新建Entity
    1. 必须要使用@Entity注解来表示此类是一个Entity类。
    2. 可以在@Entity注解上使用属性tableName =来指定此Entity对应的表的名字,如不采用则是使用默认命名。
    2. 主键设置
    1. 每个Entity至少需要定义一个主键,即使此类只有一个属性,主键不能为空
    2. 对于主键字段可以使用@PrimaryKey注解来声明此字段为主键。
    3. 如果主键比较复杂,可以在@Entity注解中使用属性promaryKeys =进行声明

    @Entity(primaryKeys = {"id", "name"})

    3. 列设置
    1. 使用@ColumnInfo来声明列信息,可以通过设置属性name =来指定列的名称,如果不设置则会采用变量的小写形式作为列名称
    2. 如果在Entity类中存在一些变量不需要生成数据表的列,可以使用@Ignore注解注释需要被忽视的属性来不生成对应的列。
    3. 如果类继承上面有父类,并且不想父类的属性生成数据表中的列,可以采用@ignoredColumns
    //父类
    open class User() {
        var num: Long? = null
    }
    
    //子类定义为数据表结构,忽视
    @Entity(tableName = "test_user", ignoredColumns = ["num"])
    class TestUserEntity(
        @PrimaryKey @ColumnInfo(name = "id") var userId: Long,
        @ColumnInfo(name = "name") var name: String? = null
    ) : User() {
    
        fun printData(): String {
            Log.d("Message", Thread.currentThread().name)
            return "id= $userId     name= $name"
        }
    }
    
    4. 嵌套Entity

    如果定义的Entity对象里面有个ChildEntity类的对象,并且希望定义的Entity中表列表字段包括包含ChildEntity类对象中的变量,可以通过注解@Embedded来实现,是来代码如下:

    data class Address(
            val street: String?,
            val state: String?,
            val city: String?,
            @ColumnInfo(name = "post_code") val postCode: Int
        )
    
        @Entity
        data class User(
            @PrimaryKey val id: Int,
            val firstName: String?,
            @Embedded val address: Address?
        )
    

    参考文章

    Google文档

    相关文章

      网友评论

          本文标题:Room数据库框架使用

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