废话也很少说了,Room数据库就是对SQLite数据库的封装,使之用起来更方便。Google也说了强烈推荐使用Room来替代SQLite。java
dependencies { ... def room_version = "2.1.0-alpha02" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" }
因为使用了Kotlin注解,请确保Gradle里配置了kotlin-kapt插件android
apply plugin: 'kotlin-kapt'
Room数据库主要由三个部分组成:web
Entity
: 数据实体,一个Entity表明一张数据表DAO
: 在这里定义数据表的操做方法Database
: 数据库下面详细来说如何实现这三个部分。
数据库
@Entity(tableName = "user") data class UserEntity(@PrimaryKey @ColumnInfo(name = "uid") var uid: Int, @ColumnInfo(name = "first_name") var firstName: String?, @ColumnInfo(name = "last_name") var lastName: String?)
要点1:在类上方添加@Entitiy
注解,并定义数据表名称
要点2:经过@PrimaryKey
来定义主键,@ColumnInfo
来定义列名称,也能够不添加注解@ColumnInfo
,那么列名称就默认是属性的名称。app
这样就至关于定义了一张表user
,user表包含uid
,first_name
, last_name
三个字段。其中uid
为主键,数据类型为Int
, first_name
和last_name
数据类型为String
,且能够为空。
ide
@Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List<UserEntity> @Query("SELECT * FROM user WHERE uid = (:userId)") fun findById(userId: Int): UserEntity @Insert fun insertAll(vararg userEntities: UserEntity) @Delete fun delete(vararg user: UserEntity) @Update fun update(vararg user: UserEntity) }
能够看到,在这里经过各类注解实现了数据表的各类增删改查方法。其中delete,并非说要传入一个彻底同样的实体,只要PrimaryKey
匹配就行。具体就很少讲了,一看就懂。svg
最重要的是要在接口前面添加注解@Dao
ui
@Database(entities = [UserEntity::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase(){ //获取数据表操做实例 abstract fun userDao(): UserDao //单例模式 companion object { private const val DB_NAME = "app_database" @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase{ val tempInstance = INSTANCE if(tempInstance != null) { return tempInstance } synchronized(this){ val instance = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, DB_NAME).build() INSTANCE = instance return instance } } } }
直接甩代码了,最重要的固然是要在类前面添加注解@Database
,在entities
中添加全部数据表的类名,version
表示数据库版本,exportSchema
表示是否生成数据库结构文件,具体能够参考: 处理Schemathis
数据库初始化方法以下:spa
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database-name").build()
要点1. 自定义的Database类必须继承RoomDatabase。
要点2. 在Database中定义获取数据表操做实例的方法。
要点3. 谷歌建议把Database实例定义成单例模式,由于数据库的初始化消耗量至关大,并且也不必在一个进程中建立多个数据库实例。
而后就能够操做数据库了,好比向数据表user插入一行数据,以下:
val user = UserEntity(1, "rx", "chen") AppDatabase.getDatabase(this).userDao().insertAll(user)
插入以后查看user数据表:
重要提示:Room数据库的操做不能在UI线程执行,请勿在主线程直接调用上述代码
除了user
表,我还有一张login
表,记录用户的登陆次数和最后登陆时间,以下:
@Entity(tableName = "login") data class LoginEntity(@PrimaryKey @ColumnInfo(name = "id") var uid: Int, @ColumnInfo(name = "login_count") var loginCount: Int, @ColumnInfo(name = "latest_time") var latestTime: String?)
如今我想知道每一个用户的登陆次数,获得每一个用户的名字和登陆次数。
首先,对应咱们须要数据,创建一个相应的数据类,以此来接受获取的数据
class UserLoginCount(var last: String, var first: String, var loginCount: Int) { val userName: String get() = "$first $last" override fun toString(): String { return "name: $userName, loginCount: $loginCount" } }
而后建立一个联表查询的DAO
,在其中定义好查询方法:
@Dao interface JoinDao { @Query("SELECT user.first_name as first, " + "user.last_name as last, " + "login.login_count as loginCount " + "FROM login, user WHERE user.uid = login.id ") fun getUserLoginCount(): List<UserLoginCount> }
在Database类中定义获取JoinDao的方法:
abstract fun joinDao(): JoinDao
最后就能够直接查询了:
val userLoginCounts = AppDatabase.getDatabase(this).joinDao().getUserLoginCount()
打印出userLoginCounts,结果以下:
name: rx chen, loginCount: 12
name: julia jx, loginCount: 3
如今咱们给user
表添加一个字段age
,数据库版本从原来的1
升到2
,Database类修改以下:
@Database(entities = [UserEntity::class, LoginEntity::class], version = 2, exportSchema = false) abstract class AppDatabase : RoomDatabase(){ //获取数据表操做实例 abstract fun userDao(): UserDao abstract fun loginDao(): LoginDao abstract fun joinDao(): JoinDao //单例数据库 companion object { private const val DB_NAME = "app_database" @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase{ val tempInstance = INSTANCE if(tempInstance != null){ return tempInstance } synchronized(this){ val instance = Room .databaseBuilder(context.applicationContext, AppDatabase::class.java, DB_NAME) .fallbackToDestructiveMigration() .addMigrations(MIGRATION_1_2) .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") } } } }
同时,别忘了给UserEntity添加age属性
@Entity(tableName = "user") data class UserEntity(@PrimaryKey @ColumnInfo(name = "uid") var uid: Int, @ColumnInfo(name = "first_name") var firstName: String?, @ColumnInfo(name = "last_name") var lastName: String?, @ColumnInfo(name = "age") var age: Int = 0)
这里最重要的是数据库初始化的时候添加了两个方法:
一个是 fallbackToDestructiveMigration()
,这个方法的做用是,当数据库升级没法匹配时,好比执行的升级是从版本2升到版本3,可是却找不到版本2(有可能这时仍然是版本1),此时就会按照最新的版本3重建数据库,那么以前的数据库就会被删除,数据也会被清掉。若是不添加这个方法,遇到这种状况会致使app崩溃。
第二个方法固然是添加升级的方法addMigrations()
,这个方法能够接受多个升级语句,好比当前要升级到版本3,但考虑到一部分用户已是版本2,一部分用户仍然在用版本1,那么此时应该提供两种升级方式:从版本1升级到版本3 和 从版本2升级到版本3。写好两种升级语句,一块儿传给addMigrations()
方法便可。
执行数据库初始化方法后,数据表user
已经添加了age
字段,以下:
PS:数据库升级时写的修改数据表字段或者添加数据表的SQL语句,必定要和Entity定义的内容严格对应,好比我在
UserEntity
中定义的age
字段是非空的,那么SQL语句中就必定要写上age INTEGER NOT NULL
,否则就会升级失败甚至致使app崩溃。