Android Room 官方文档
Room Persistence Library(官网文档翻译)
在kotlin中使用room(Room Persistence Library)和遇到的坑
Android官方ORM框架ROOM(Google I/O 2017)
对 Room
不了解的可以看上面文章就可以了,我就不做过多赘述。每个例子都有一个环境或场景,好吧,场景如下:
描述
用户拥有哪些书
User
字段 | 说明 |
---|---|
u_id | 序号 |
u_name | 姓名 |
u_phone | 电话 |
u_create_date | 创建日期 |
u_update_date | 更新日期 |
u_status | 状态 |
Book
字段 | 说明 |
---|---|
b_id | 序号 |
b_name | 书名 |
b_author | 作者 |
b_copyright | 出版 |
b_price | 价格 |
b_status | 状态 |
user_id | 用户id |
kotlin 环境不再赘述,自行操作,或者直接使用 Android Studio 3.0
新建kotlin 工程。
如果是 3.0 建的kotlin
项目, 则 project/build.gradle
下自己带有 google maven
仓库。
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
如果是 2.x 配置的 kotlin
环境,则在 project/build.gradle
下配置google maven
仓库:
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
参考(墙):https://developer.android.com/topic/libraries/architecture/adding-components.html
配置 Room 依赖
如果是 3.0 推荐使用下面方式
implementation 'android.arch.persistence.room:runtime:1.0.0-alpha5'
// kotlin
kapt "android.arch.persistence.room:compiler:1.0.0-alpha5"
// java
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"
// 测试 room
testCompile 'android.arch.persistence.room:testing:1.0.0-alpha5'
// 配合 rxjava2 使用
implementation 'android.arch.persistence.room:rxjava2:1.0.0-alpha5'
如果 是 2.x ,将 implementation
换成 compile
即可,注意:
如果是 kotlin
环境,使用 kapt
来操作注解:
kapt "android.arch.persistence.room:compiler:1.0.0-alpha5"
如果是 java
环境,使用 annotationProcessor
来操作注解:
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"
当然,同时放置也没事。
先看看要实现的内容,
table (entity) 实现时,注意实现其构造函数,否则报错;有些字段不支持,通过 TypeConverters 处理。
注解说明:
@Entity
指明表,默认表名为实体类名,也可通过 tableName
指定表名@PrimaryKey
主键,自增的话指明 autoGenerate
为 true
@ColumnInfo
指定列,默认列表为属性名,也可通过name
指定列名UserTable
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
import java.util.*
/**
* Created by yuan on 02/08/2017.
* 用户表
* id 自增
*/
@Entity(tableName = "user")
data class UserTable constructor(@ColumnInfo(name = "u_id")
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
@ColumnInfo(name = "u_name")
var name: String? = null,
@ColumnInfo(name = "u_phone")
var phone: String? = null,
@ColumnInfo(name = "u_create_date")
var createDate: Date? = null,
@ColumnInfo(name = "u_update_date")
var updateDate: Date? = null,
@ColumnInfo(name = "u_status")
var status: Int? = 0
) {
constructor() : this(0)
}
BookTable
外键可通过 foreignKeys
进行指定,foreignkeys
为数组使用 arrayOf
操作,如下所示:
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.ForeignKey
import android.arch.persistence.room.PrimaryKey
/**
* Created by yuan on 02/08/2017.
* book table
* 注意:数据类型必须为其支持的类型
*/
@Entity(tableName = "book", foreignKeys = arrayOf(ForeignKey(entity = UserTable::class,
parentColumns = arrayOf("u_id"),
childColumns = arrayOf("user_id")))
)
data class BookTable(
@ColumnInfo(name = "b_id")
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
@ColumnInfo(name = "b_name")
var name: String = "",
@ColumnInfo(name = "b_author")
var author: String = "",
@ColumnInfo(name = "b_price")
var price: Double = 0.toDouble(),
@ColumnInfo(name = "b_copyright")
var copyright: String = "",
@ColumnInfo(name = "b_status")
var status: Int = 0,
@ColumnInfo(name = "user_id")
var userId: Int = 0
) {
// 必须有公共构造方法
constructor() : this(0)
}
Dao 是一个接口
注解说明:
@Dao
指明为 Dao
类@Insert
插入操作@Update
更新操作@Delete
删除操作@Query
查询操作 ,需要指定 Sql
语句更多操作,下篇会出个实例讲解
UserDao
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import cn.labelnet.android.roomdb.base.data.tables.UserTable
/**
* Created by yuan on 02/08/2017.
* 用户 dao
*/
@Dao
interface UserDao {
/**
* 插入一个用户
*/
@Insert
fun insertUser(user: UserTable)
/**
* 插入多个用户
*/
/**
* 查询全部用户
*/
@Query("select * from user")
fun selectUsers(): List
}
BookDao
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import cn.labelnet.android.roomdb.base.data.tables.BookTable
/**
* Created by yuan on 02/08/2017.
* book dao
*/
@Dao
interface BookDao {
@Insert
fun insertBook(book: BookTable)
@Query("select * from book")
fun selectBooks(): List
}
抽象类,需要继承
RoomDatabase
注解说明
@Database
指定操作,将 Dao
层全部集中在一起,可以指定 version
使用 entities
指定所用的 entity(table)
@TypeConverters
指定转换器,看要加在什么上面import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters
import cn.labelnet.android.roomdb.base.data.Converters
import cn.labelnet.android.roomdb.base.data.dao.BookDao
import cn.labelnet.android.roomdb.base.data.dao.UserDao
import cn.labelnet.android.roomdb.base.data.tables.BookTable
import cn.labelnet.android.roomdb.base.data.tables.UserTable
/**
* Created by yuan on 02/08/2017.
* 操作用户的 database
*/
@Database(entities = arrayOf(UserTable::class, BookTable::class), version = 1)
@TypeConverters(Converters::class)
abstract class AppDataBase : RoomDatabase() {
/**
* user 操作 dao
*/
abstract fun userDao(): UserDao
/**
* book 操作 dao
*/
abstract fun bookDao(): BookDao
}
有些类型不可以直接操作,就需要 converter
进行转换,比如日期类型。
上面 UserTable
中的 createDate
为 Date
类型,在生成数据表的存储的时候用 String
进行存储,这是使用 converter
。 当然 Long
类型存储日期时间也是可以的,不过就不是下面写法了,自己另行写吧。
@ColumnInfo(name = "u_create_date")
var createDate: Date? = null,
注解说明:
@TypeConverter
指明转换方法@TypeConverters
作用于需要转换的地方如果作用于 Datebase
上,那么整个数据库中日期类型都按照这样的格式转换。
如果作用于 Entity(table)
上,那么就只有该 Entity(table)
转换。
import android.arch.persistence.room.TypeConverter
import java.text.SimpleDateFormat
import java.util.*
/**
* Created by yuan on 02/08/2017.
* 转换器
*/
class Converters {
val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
/**
* 时间戳转日期
*/
@TypeConverter
fun fromTimestamp(value: String?): Date {
if (value == null) {
return Date(System.currentTimeMillis())
}
synchronized(format) {
return format.parse(value)
}
}
/**
* 日期转时间
*/
@TypeConverter
fun dateToTimestamp(date: Date?): String {
if (date == null) {
val nowDate = Date(System.currentTimeMillis())
synchronized(format) {
return format.format(nowDate)
}
}
synchronized(format) {
return format.format(date)
}
}
}
转换器 存储结果:
使用单例方式就行初始化db
, 生成数据库操作对象:
val appDb = Room.databaseBuilder(context,
AppDataBase::class.java,
DB_NAME)
.build()
关闭数据库
appDb.close()
数据库版本更新
val appDb = Room.databaseBuilder(context,
AppDataBase::class.java,
DB_NAME)
.addMigrations(//实现 Migration )
.build()
完整代码
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Room
import android.arch.persistence.room.migration.Migration
import android.content.Context
import android.util.Log
import cn.labelnet.android.roomdb.base.data.dao.BookDao
import cn.labelnet.android.roomdb.base.data.dao.UserDao
import cn.labelnet.android.roomdb.base.data.database.AppDataBase
/**
* Created by yuan on 02/08/2017.
* db 操作类
*/
class DbHelper constructor(context: Context) {
var appDb: AppDataBase? = null
val DB_NAME = "room_test_db.db"
/**
* 初始化 db
*/
init {
appDb = Room.databaseBuilder(context,
AppDataBase::class.java,
DB_NAME)
.build()
//.addMigrations(MigrateDb(1, 2))
}
/**
* 获取 user dao
*/
fun getUserDao(): UserDao {
return appDb!!.userDao()
}
/**
* 获取 book dao
*/
fun getBookDao(): BookDao {
return appDb!!.bookDao()
}
/**
* 单例实现
*/
companion object {
var INSTANCE: DbHelper? = null
fun init(context: Context): DbHelper {
if (INSTANCE == null) {
synchronized(DbHelper::class) {
if (INSTANCE == null) {
INSTANCE = DbHelper(context)
}
}
}
return INSTANCE!!
}
}
/**
* 关闭数据库
*/
fun onDestory() {
if (appDb != null) {
appDb!!.close()
}
}
/**
* 版本合并
*/
class MigrateDb(startVersion: Int, endVersion: Int) : Migration(startVersion, endVersion) {
override fun migrate(database: SupportSQLiteDatabase?) {
Log.v("DbHelper", "migrate")
}
}
}
Question 1
Caused by: java.lang.RuntimeException: cannot find implementation for AppDatabase_Impl does not exist
at android.arch.persistence.room.Room.getGeneratedImplementation(Room.java:90)
at android.arch.persistence.room.RoomDatabase$Builder.build(RoomDatabase.java:340)
at com.ttp.kotlin.kotlinsample.room.AppDatabase$Companion.getInMemoryDatabase(AppDatabase.kt:19)
解决: kotlin
下使用 kapt
将
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"
换成
kapt "android.arch.persistence.room:compiler:1.0.0-alpha5"
Question 2
Error:Entities and Pojos must have a usable public constructor.
解决
entity
需要实现构造函数
@Entity(tableName = "user")
data class UserTable constructor(@ColumnInfo(name = "u_id")
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
@ColumnInfo(name = "u_name")
var name: String? = null,
@ColumnInfo(name = "u_phone")
var phone: String? = null,
@ColumnInfo(name = "u_create_date")
var createDate: Date? = null,
@ColumnInfo(name = "u_update_date")
var updateDate: Date? = null,
@ColumnInfo(name = "u_status")
var status: Int? = 0
) {
// 实现构造函数
constructor() : this(0)
}
Question 3
Warning:warning: Supported source version 'RELEASE_7' from annotation processor 'android.arch.persistence.room.RoomProcessor' less than -source '1.8'
Warning:warning: Supported source version 'RELEASE_7' from annotation processor 'android.arch.lifecycle.LifecycleProcessor' less than -source '1.8'
解决:使用 kapt
时,不需要重复在build.gradle
上进行引入 kotlin-kapt
, 当然没有引入最好了。
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
// 不需要它,已经默认有了,它导致上面
//apply plugin: 'kotlin-kapt'
Question 4
Error:Cannot figure out how to save this field into database. You can consider adding a type converter for it.
Error:Cannot figure out how to read this field from a cursor.
解决:有些字段或类型不认识,需要在 converter 进行转换;
class Converters {
val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
/**
* 时间戳转日期
*/
@TypeConverter
fun fromTimestamp(value: String?): Date {
if (value == null) {
return Date(System.currentTimeMillis())
}
synchronized(format) {
return format.parse(value)
}
}
/**
* 日期转时间
*/
@TypeConverter
fun dateToTimestamp(date: Date?): String {
if (date == null) {
val nowDate = Date(System.currentTimeMillis())
synchronized(format) {
return format.format(nowDate)
}
}
synchronized(format) {
return format.format(date)
}
}
}
Question 5
Process: cn.labelnet.android.roomdb, PID: 22630
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:143)
at android.arch.persistence.room.RoomDatabase.beginTransaction(RoomDatabase.java:190)
at cn.labelnet.android.roomdb.base.data.dao.UserDao_Impl.insertUser(UserDao_Impl.java:63)
at cn.labelnet.android.roomdb.main.MainActivity.onItemClick(MainActivity.kt:53)
at cn.labelnet.android.roomdb.main.adapter.MainAdapter$onBindViewHolder$1.onClick(MainAdapter.kt:53)
at android.view.View.performClick(View.java:5637)
at android.view.View$PerformClick.run(View.java:22429)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
解决 : 数据库操作放在 io 线程,不要在 ui 线程做。子线程操作自行解决,我这里就不赘述了。
Question 6
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
at android.arch.persistence.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:112)
at android.arch.persistence.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:93)
at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$1.onOpen(FrameworkSQLiteOpenHelper.java:64)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:266)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)
解决:数据库改变,需要使用版本更新操作。
Room.databaseBuilder(context,
AppDataBase::class.java,
DB_NAME)
.addMigrations(MigrateDb(1, 2))
.build()
/**
* 版本合并
*/
class MigrateDb(startVersion: Int, endVersion: Int) : Migration(startVersion, endVersion) {
override fun migrate(database: SupportSQLiteDatabase?) {
// 版本更新操作
}
}
以开源,仓库地址 : RoomDb
下篇说明说用