0
点赞
收藏
分享

微信扫一扫

Android - Android Architecture Components - Room 在 Kotlin 中使用 (1)


参考文章

​​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

1. 环境配置

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"

当然,同时放置也没事。

2. 基本使用

先看看要实现的内容,

Android - Android Architecture Components  - Room 在 Kotlin 中使用 (1)_android

  • entity ( table )
  • dao
  • database
  • converters
  • Dbhelper

2.1 table 实现

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)
}

2.2 Dao 实现

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<UserTable>
}

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<BookTable>
}

2.3 Database

抽象类,需要继承 ​​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

}

2.4 converters

有些类型不可以直接操作,就需要 ​​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)
}
}
}

转换器 存储结果:

Android - Android Architecture Components  - Room 在 Kotlin 中使用 (1)_android_02

2.5 DbHelper

使用单例方式就行初始化​​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")
}
}

}

3. 问题与坑

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 进行转换;

  • Date
  • ArrayList

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?) {
// 版本更新操作
}
}

4. 最后

以开源,仓库地址 : ​​RoomDb​​

下篇说明说用


举报

相关推荐

0 条评论