何为 Room
Room
是 Google jetpack 体系的一个数据库框架,近年来 Google 力推该框架,作为开发者,我们也需拥抱新技术。
Room
对 SQLite
的抽象,是为了让 SQLite
更方便Android开发者使用而研发的一个数据库框架
下面引用 Android 开发者指南对 Room
的优势说明
In particular, Room provides the following benefits:
- Compile-time verification of SQL queries.
- Convenience annotations that minimize repetitive and error-prone boilerplate code.
- Streamlined database migration paths.
引用自Save data in a local database using Room | Android Developers
翻译过来,就是以下三个优势
- 会对SQL在编译阶段进行验证
- 有方便我们使用的注解,可以大幅度减少重复编写和出错
- 数据库更容易迁移
- 可以和多种三方库桥接进行异步查询
Room
的使用效率如何
Room
的基本使用
了解完 Room
是什么,还等什么,快速上手 Room
吧
库的导入
要使用 Room
,我们先要在 build.gradle(:app)
里面引入该库
plugins { ... //if use kapt id 'kotlin-kapt' } ... dependencies { def room_version = "2.4.3" //base implementation "androidx.room:room-runtime:$room_version" // To use Java annotation processing tool annotationProcessor "androidx.room:room-compiler:$room_version" // To use Kotlin annotation processing tool (kapt) kapt "androidx.room:room-compiler:$roomVersion" ... } 复制代码
我们需要引入 Room
的时候,一定需要的是 runtime 库和对应的一个注解处理器 的库,上面的代码我引入了 java 和kotlin 两种类型,实际使用中,我们引入一种就好。注解处理器一定是要引入的,这样子我们才能根据注解在编译期生成对应的实现类。
如果引入的是 kapt
,记得要在插件声明处声明对应的插件。
数据实体(Entity
)
@Entity(tableName = "users") data class User( @PrimaryKey(autoGenerate = true) val uid: Int?, @ColumnInfo(name = "first_name") val firstName: String?, @ColumnInfo(name = "last_name") val lastName: String? ) 复制代码
作用:用于表示应用的数据库中的表。
上面代码是数据实体类的,我们需要对类写一个 @Entity()
注解,tableName = "users"
是对表的命名,否则表就自动以类名做表名。
接下来我们来看下面的数据变量
@PrimaryKey()
表示的是主键,添加 autoGenerate = true
表示输入的id为空时候,允许自动赋值。每一个表都是需要有一列做主键
@ColumnInfo(name = "first_name")
表示的是为该字段的对应列命名。若是不使用该注解,列名则自动为字段名称
数据访问对象(DAO
)
@Dao interface Dao { @Query("SELECT * FROM user") fun getAll(): List<User> @Query("SELECT * FROM user WHERE uid IN (:userIds)") fun loadAllByIds(userIds: IntArray): List<User> @Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1") fun findByName(first: String, last: String): User @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(vararg users: User) @Delete fun delete(user: User) } 复制代码
作用:提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。
上文代码的数据库访问对象,我们写的时候其实就是写一个对应的接口,对接口里面的方法附上相关的注解。然后在代码的编译阶段,就会根据注解生成对应的代码。
上文的代码中我们可以看到,有部分的方法注解里面会用到 SQL 语句,但是有部分就不需要 SQL 语句。这是由于 Room
给我们提供了两种不同的查询方法,一种是自带插入 Insert
、Update
、Delete
的 便捷方法,一种是自己编写 SQL 语句的自定义查询方法
There are two types of DAO methods that define database interactions:
- Convenience methods that let you insert, update, and delete rows in your database without writing any SQL code.
- Query methods that let you write your own SQL query to interact with the database.
两种方法配合使用,我们就可以低成本使用且易复用 SQLite 的强大功能了。使用查询方法的前提是我们会 SQL 语法,还不懂的同学,需要先学会 SQL 语法
在线就能用的 SQL 练习平台我给你找好了! - 知乎 (zhihu.com)
数据库类(Database
)
@Database(entities = [User::class], version = 2) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): Dao } 复制代码
作用:用于保存数据库并作为应用持久性数据底层连接的主要访问点。
这个类是为建立和保存数据库所使用的,有以下三个要点
- 新增一个
RoomDatabase
的abstract
子类,需要是抽象类- 子类需加注解
@Database(entities = [xxx], version = n)
,entities
包含数据实体,将会在这个数据库中创建对应的表,version
是数据的版本号- 对于与数据库关联的每个DAO类,数据库类必须定义一个无参的抽象方法,并返回DAO类实例
@Database
该注解包含列出所有与数据库关联的数据实体的 entities
数组。也就是说,当我们有多个表的时候,需要把所有表都置于该注解上边。例如,两个表的时候,entities = [User::class, single::class]
@Database
注解的另一个 version
参数,是为数据库的版本号。当我们要变动数据库的表的时候,如果直接变动,不做迁移(更新),程序是会崩溃的。应用 autoMigrations = [AutoMigration (from = 1, to = 2)]
这个注解参数,可以实现自动的迁移。示例代码如下,更多的迁移方法可以查看指南 Migrating Room databases | Android Developers
// Database class before the version update. @Database( version = 1, entities = [User::class] ) abstract class AppDatabase : RoomDatabase() { ... } // Database class after the version update. @Database( version = 2, entities = [User::class], autoMigrations = [ AutoMigration (from = 1, to = 2) ] ) abstract class AppDatabase : RoomDatabase() { ... } 复制代码
创建使用数据库
val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "database-name" ).build() val userDao = db.userDao() val user1 = User(null,"hhhh","zhang") val user2 = User(1,"ddd","nishone") binding.button.setOnClickListener { Thread{ userDao.insertAll(user1,user2) list= userDao.getAll() if (list != null){ Log.d(TAG, "onCreate: ${list!![1].firstName}") }else{ Log.d(TAG, "onCreate: ") } }.start() } 复制代码
由于数据库的创建和使用都是耗时操作,在开发中,没有使用到多进程的时候,我们谨记要遵循单例模式来创建实例;在调用增上改查时候,我们需要在子线程中来完成这个操作。
注意:如果您的应用在单个进程中运行,在实例化
AppDatabase
对象时应遵循单例设计模式。每个RoomDatabase
实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。如果您的应用在多个进程中运行,请在数据库构建器调用中包含
enableMultiInstanceInvalidation()
。这样,如果您在每个进程中都有一个AppDatabase
实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中AppDatabase
的实例。
Room
的表关系
日常开发中,我们数据库的表经常要做到和其他的表关联的,各个表直接也都是有关系的。而 Room
自然也提供了关于表联立的用法给我们,要注意的是,它不提供实体直接引用的形式。
关于表的关系,大家请查看官方文档,官方文档有详细的说明。
定义对象之间的关系 | Android 开发者 | Android Developers