Room数据库框架使用
概述
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(数据表操作)。
常规的操作流程如下:
APP使用DataBase来获取与该数据关联的数据表操作对象:DAO。
应用使用每个DAO从数据库中获取实体:Entity。
应用对Entity进行更改或获取数据。
应用将对Entity的更改通过DAO保存到数据库中。
具体的结构如下图所示:
room框架.png
DataBase
Database主要是包含了DAO并且提供创建和链接数据库的方法。
1. 创建数据库
创建DataBase主要包括如下几个步骤:
创建继承
RoomDatabase
的抽象类在继承类前使用注解
@Database
声明数据库结构的Entity并设置数据库版本号。
数据库实例最好能够定义为单例。
示例代码如下:
@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需要注意如下几点:
DAO必须是抽象类或者接口。
DAO必须要使用
@Dao
注解进行标识。
DAO示例代码如下:
@Daointerface 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. 插入数据
插入数据方法需要在方法前使用
@Insert
注解进行标识。当参数只有一个时,则它可以返回 long,这是插入项的新 rowId。
如果参数是数组或集合,则应返回 long[] 或 List<Long>。
查看编译文件,可以发现插入操作是在一个独立的事务中完成的,保证了插入操作的原子性。
@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
Update方法需要采用注解
@Update
来完成Update方法使用和每个实体的主键匹配查询
Update方法也可以返回Int值,表示数据库中更新的行数。
@Dao interface MyDao { @Update fun updateUsers(vararg users: User) }
3. 删除
Delete方法需要采用注解
@Delete
进行标识Delete方法使用的是主键查询要删除的实体。
Delete方法也可以返回Int值,表示数据库中更新的行数。
@Dao interface MyDao { @Delete fun deleteUsers(vararg users: User) }
4. 查询
查询方法需要使用
@Query
注解进行标识@Query
不仅仅可以用来标识查询方法,还可以标识其他的任意SQL语句每个
@Query
方法在编译时就是检查SQL语句是否正确,如果存在错误则直接编译报错,而不是运行时失败。
@Dao interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge") fun loadAllUsersOlderThan(minAge: Int): Array<User> }
5. 带参数查询
在查询过程通添加条件不可避免的需要添加参数,可以在SQL语句中使用
:param
的方式引入参数。如果编译检查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. 传递参数的集合
存在有部分查询传入的数量不定的参数,参数的确切数量要到运行时才能得到,可以采用如下的参数合集的方式进行完成。
@Dao interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") fun loadUsersFromRegions(regions: List<String>): List<NameTuple> }
7. 查询语句返回对象类型
如果需要返回实体类的部分字段,可以通过新建一个只包含这部分字段的类,返回返回此新类的集合对象。
查询语句还支持分布其他的返回类型,例如
RxJava中的Flowable<T>
等。DAO里的方法不能够放在主线程执行,因此需要等待
可以对DAO里的方法添加
suspend
关键字,以使用kotlin协程功能使得这个方法成为异步方法LiveData:如果返回为LiveData对象,则不需要进行异步操作,因为room会自动完成异步操作。
数据库查询类型.png
8. 事务
实现自定义事务需要添加如下注解
@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对象和数据表的对应关系如下:
一个Entity对象代表数据表中的一行
Entity类对应一个数据表,其成员变量对应数据表中的列
新建一个Entity可以从如下几个方面进行设置
1. 新建Entity
必须要使用
@Entity
注解来表示此类是一个Entity类。可以在
@Entity
注解上使用属性tableName =
来指定此Entity对应的表的名字,如不采用则是使用默认命名。
2. 主键设置
每个Entity至少需要定义一个主键,即使此类只有一个属性,主键不能为空
对于主键字段可以使用
@PrimaryKey
注解来声明此字段为主键。如果主键比较复杂,可以在
@Entity
注解中使用属性promaryKeys =
进行声明
@Entity(primaryKeys = {"id", "name"})
3. 列设置
使用
@ColumnInfo
来声明列信息,可以通过设置属性name =
来指定列的名称,如果不设置则会采用变量的小写形式作为列名称如果在Entity类中存在一些变量不需要生成数据表的列,可以使用
@Ignore
注解注释需要被忽视的属性来不生成对应的列。如果类继承上面有父类,并且不想父类的属性生成数据表中的列,可以采用
@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? )
作者:静水红阳
链接:https://www.jianshu.com/p/f63d00365195