Android - Room

一、简介

Jetpack 中的框架,它主要由Entity、Dao和Database这3部 分组成,每个部分都有明确的职责:

1、Entity。用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并 且表中的列是根据实体类中的字段自动生成的。

2、Dao。Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际 编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。

3、Database。用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提 供Dao层的访问实例。

dependencies {
 ...
 implementation "androidx.room:room-runtime:2.1.0"
 kapt "androidx.room:room-compiler:2.1.0"
}

新增了一个kotlin-kapt插件,同时在dependencies闭包中添加了两个Room的依赖库。

由于Room会根据我们在项目中声明的注解来动态生成代码,因此这里一定要使用kapt引入的编译时注解库,而启用编译时注解功能则一定要先添加kotlin-kapt插件。

注意,kapt 只能在Kotlin项目中使用,如果是Java项目的话,使用annotationProcessor即可。

二、使用

1、首先是定义Entity,也就 是实体类。

2、然后在 User类中添加了一个id字段,并使用@PrimaryKey注解将它设为了主键,再把 autoGenerate参数指定成true,使得主键的值是自动生成的

@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
 @PrimaryKey(autoGenerate = true)
 var id: Long = 0
}

3、这样实体类部分就定义好了,不过这里简单起见,只定义了一个实体类,在实际项目当中,你 可能需要根据具体的业务逻辑定义很多个实体类。当然,每个实体类定义的方式都是差不多 的,最多添加一些实体类之间的关联。

4、接下来开始定义Dao,这部分也是Room用法中最关键的地方,因为所有访问数据库的操作都是 在这里封装的。

//所有访问数据库的操作都是在这里封装的。
//Dao要做的事情就是覆盖所有的业务需求,使得业务方永远只需要与Dao 层进行交互,而不必和底层的数据库打交道。
@Dao
interface UserDao {

    @Insert
    fun insterUser(user: User): Long

    @Update
    fun updateUser(newUser: User)

    @Query("select * from User")
    fun loadAllUsers(): List<User>

    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>
    
    @Delete
    fun deleteUser(user: User)
    
    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String): Int
}

5、UserDao接口的上面使用了一个@Dao注解,这样Room才能将它识别成一个Dao。UserDao的 内部就是根据业务需求对各种数据库操作进行的封装。数据库操作通常有增删改查这4种,因此 Room也提供了@Insert、@Delete、@Update和@Query这4种相应的注解。

6、可以看到,insertUser()方法上面使用了@Insert注解,表示会将参数中传入的User对象插 入数据库中,插入完成后还会将自动生成的主键id值返回。updateUser()方法上面使用了 @Update注解,表示会将参数中传入的User对象更新到数据库当中。deleteUser()方法上面 使用了@Delete注解,表示会将参数传入的User对象从数据库中删除。以上几种数据库操作都 是直接使用注解标识即可,不用编写SQL语句。

7、但是如果想要从数据库中查询数据,或者使用非实体类参数来增删改数据,那么就必须编写 SQL语句了。比如说我们在UserDao接口中定义了一个loadAllUsers()方法,用于从数据库 中查询所有的用户,如果只使用一个@Query注解,Room将无法知道我们想要查询哪些数据, 因此必须在@Query注解中编写具体的SQL语句才行。我们还可以将方法中传入的参数指定到 SQL语句当中,比如loadUsersOlderThan()方法就可以查询所有年龄大于指定参数的用 户。另外,如果是使用非实体类参数来增删改数据,那么也要编写SQL语句才行,而且这个时 候不能使用@Insert、@Delete或@Update注解,而是都要使用@Query注解才行,参考 deleteUserByLastName()方法的写法。

8、进入最后一个环节:定义Database。

这部分内容的写法是非常固定的,只需要定义 好3个部分的内容:数据库的版本号、包含哪些实体类,以及提供Dao层的访问实例。新建一个 AppDatabase.kt文件

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

    companion object {
        private var instance: AppDatabase? = null

        @Synchronized
        fun getDataBase(context: Context): AppDatabase {
            instance?.let {
                return it
            }
            return Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java, "app_database"
            )
                .build().apply {
                    instance = this
                }
        }
    }
}

9、可以看到,这里我们在AppDatabase类的头部使用了@Database注解,并在注解中声明了数 据库的版本号以及包含哪些实体类,多个实体类之间用逗号隔开即可。

10、另外,AppDatabase类必须继承自RoomDatabase类,并且一定要使用abstract关键字将它 声明成抽象类,然后提供相应的抽象方法,用于获取之前编写的Dao的实例,比如这里提供的 userDao()方法。不过我们只需要进行方法声明就可以了,具体的方法实现是由Room在底层 自动完成的。

11、紧接着,我们在companion object结构体中编写了一个单例模式,因为原则上全局应该只存 在一份AppDatabase的实例。这里使用了instance变量来缓存AppDatabase的实例,然后 在getDatabase()方法中判断:如果instance变量不为空就直接返回,否则就调用 Room.databaseBuilder()方法来构建一个AppDatabase的实例。databaseBuilder() 方法接收3个参数,注意第一个参数一定要使用applicationContext,而不能使用普通的 context,否则容易出现内存泄漏的情况,关于applicationContext的详细内容我们将会 在第14章中学习。第二个参数是AppDatabase的Class类型,第三个参数是数据库名,这些都 比较简单。最后调用build()方法完成构建,并将创建出来的实例赋值给instance变量,然 后返回当前实例即可。

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">
 ...
 <Button
 android:id="@+id/getUserBtn"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_gravity="center_horizontal"
 android:text="Get User"/>
 <Button
 android:id="@+id/addDataBtn"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_gravity="center_horizontal"
 android:text="Add Data"/>
 <Button
 android:id="@+id/updateDataBtn"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_gravity="center_horizontal"
 android:text="Update Data"/>
 <Button
 android:id="@+id/deleteDataBtn"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_gravity="center_horizontal"
 android:text="Delete Data"/>
 <Button
 android:id="@+id/queryDataBtn"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_gravity="center_horizontal"
 android:text="Query Data"/>
</LinearLayout>

class MainActivity : AppCompatActivity() {
 ...
 override fun onCreate(savedInstanceState: Bundle?) {
 ...
 val userDao = AppDatabase.getDatabase(this).userDao()
 val user1 = User("Tom", "Brady", 40)
 val user2 = User("Tom", "Hanks", 63)
 addDataBtn.setOnClickListener {
 thread {
 user1.id = userDao.insertUser(user1)
 user2.id = userDao.insertUser(user2)
 }
 }
 updateDataBtn.setOnClickListener {
 thread {
 user1.age = 42
 userDao.updateUser(user1)
 }
 }
 deleteDataBtn.setOnClickListener {
 thread {
 userDao.deleteUserByLastName("Hanks")
 }
 }
 queryDataBtn.setOnClickListener {
 thread {
 for (user in userDao.loadAllUsers()) {
 Log.d("MainActivity", user.toString())
 }
 }
 }
 }
 ...
}

这段代码的逻辑还是很简单的。首先获取了UserDao的实例,并创建两个User对象。然后 在“Add Data”按钮的点击事件中,我们调用了UserDao的insertUser()方法,将这两个 User对象插入数据库中,并将insertUser()方法返回的主键id值赋值给原来的User对象。之所以要这么做,是因为使用@Update和@Delete注解去更新和删除数据时都是基于这个id值 来操作的。

然后在“Update Data”按钮的点击事件中,我们将user1的年龄修改成了42岁,并调用 UserDao的updateUser()方法来更新数据库中的数据。在“Delete Data”按钮的点击事件 中,我们调用了UserDao的deleteUserByLastName()方法,删除所有lastName是Hanks 的用户。在“Query Data”按钮的点击事件中,我们调用了UserDao的loadAllUsers()方 法,查询并打印数据库中所有的用户

另外,由于数据库操作属于耗时操作,Room默认是不允许在主线程中进行数据库操作的,因此 上述代码中我们将增删改查的功能都放到了子线程中。不过为了方便测试,Room还提供了一个 更加简单的方法,如下所示:

Room.databaseBuilder(context.applicationContext, AppDatabase::class.java,"app_database")
 .allowMainThreadQueries()
 .build()

猜你喜欢

转载自blog.csdn.net/m0_59482482/article/details/130214257