之前我们讲过使用内置的ORM框架进行数据的缓存,现在问题来了,有的同学项目已经使用了官方推荐的Room数据库,那怎么办呢?安排。
我们知道,数据缓存有个非常重要的角色是Repository,比如内置的就用的DoraDatabaseCacheRepository。我们现在来定义一个Room的。
```
package dora.cache.repository
import android.content.Context
import androidx.room.RoomDatabase
import dora.cache.holder.*
abstract class RoomDatabaseCacheRepository<T, D : RoomDatabase>(context: Context)
: BaseDatabaseCacheRepository<T>(context) {
override fun createCacheHolder(clazz: Class<T>): CacheHolder<T> {
return RoomCacheHolder<T>(getRoomDatabase(), getDaoName(), clazz)
}
override fun createListCacheHolder(clazz: Class<T>): CacheHolder<MutableList<T>> {
return RoomListCacheHolder<T>(getRoomDatabase(), getDaoName(), clazz)
}
abstract fun getRoomDatabase() : D
/**
* 返回RoomDatabase的getDao的方法名。
*/
abstract fun getDaoName() : String
}
```
同样的,我们要把两个CacheHolder也定义一下啦。
```
package dora.cache.holder
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import dora.cache.holder.dao.IRoomDao
import dora.db.OrmLog
import dora.db.builder.Condition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class RoomCacheHolder<M>(var db: RoomDatabase, var daoName: String,
var clazz: Class<M>) : CacheHolder<M> {
private lateinit var dao: IRoomDao<M>
override fun init() {
dao = db.javaClass.getDeclaredMethod(daoName).invoke(db) as IRoomDao<M>
}
override fun queryCache(condition: Condition): M? {
return runBlocking {
withContext(Dispatchers.IO) {
val query = SupportSQLiteQueryBuilder.builder(clazz.simpleName)
.selection(condition.selection, condition.selectionArgs)
.groupBy(condition.groupBy)
.orderBy(condition.orderBy)
.limit(condition.limit)
.having(condition.having).create()
dao.select(query)
}
}
}
override fun removeOldCache(condition: Condition) {
runBlocking {
withContext(Dispatchers.IO) {
val model = queryCache(condition)
model?.let {
val ok = dao.delete(it) > 0
OrmLog.d("removeOldCache:$ok")
}
}
}
}
override fun addNewCache(model: M) {
runBlocking {
withContext(Dispatchers.IO) {
val ok = dao.insert(model) > 0
OrmLog.d("addNewCache:$ok")
}
}
}
override fun queryCacheSize(condition: Condition): Long {
val query = RoomSQLiteQuery.acquire("SELECT * FROM " + clazz.simpleName + " WHERE "
+ condition.selection, condition.selectionArgs.size)
val cursor = runBlocking {
withContext(Dispatchers.IO) {
db.query(query)
}
}
if (cursor != null) {
return try {
if (cursor.moveToFirst()) {
cursor.getInt(0).toLong()
} else 0
} finally {
cursor.close()
query.release()
}
}
return 0
}
}
```
```
package dora.cache.holder
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import dora.cache.holder.dao.IListRoomDao
import dora.db.OrmLog
import dora.db.builder.Condition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class RoomListCacheHolder<M>(var db: RoomDatabase, var daoName: String,
var clazz: Class<M>) : ListCacheHolder<M>() {
private lateinit var dao: IListRoomDao<M>
override fun init() {
dao = db.javaClass.getDeclaredMethod(daoName).invoke(db) as IListRoomDao<M>
}
override fun queryCache(condition: Condition): MutableList<M>? {
return runBlocking {
withContext(Dispatchers.IO) {
val query = SupportSQLiteQueryBuilder.builder(clazz.simpleName)
.selection(condition.selection, condition.selectionArgs)
.groupBy(condition.groupBy)
.orderBy(condition.orderBy)
.limit(condition.limit)
.having(condition.having).create()
dao.select(query)
}
}
}
override fun removeOldCache(condition: Condition) {
runBlocking {
withContext(Dispatchers.IO) {
val model = queryCache(condition)
model?.let {
val ok = dao.delete(it) > 0
OrmLog.d("removeOldCache:$ok")
}
}
}
}
override fun addNewCache(models: MutableList<M>) {
runBlocking {
withContext(Dispatchers.IO) {
val results = dao.insert(models)
for (result in results) {
if (result > 0) {
continue
} else {
OrmLog.d("addNewCache:false")
return@withContext
}
}
OrmLog.d("addNewCache:true")
}
}
}
override fun queryCacheSize(condition: Condition): Long {
val query = RoomSQLiteQuery.acquire("SELECT * FROM " + clazz.simpleName + " WHERE "
+ condition.selection, condition.selectionArgs.size)
val cursor = runBlocking {
withContext(Dispatchers.IO) {
db.query(query)
}
}
if (cursor != null) {
return try {
if (cursor.moveToFirst()) {
cursor.getInt(0).toLong()
} else 0
} finally {
cursor.close()
query.release()
}
}
return 0
}
}
```
由于Room需要配合协程去操作数据库,所以你也要依赖协程的库。
```
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
def room_version = "2.2.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
```
由于Room源码使用到了apt,你也要配置apt的环境。
```
plugins {
id 'kotlin-kapt'
}
```
这里也有可能是apply。
```
android {
defaultConfig{
javaCompileOptions {
annotationProcessorOptions {
arguments = [
"room.schemaLocation":"$projectDir/schemas".toString(),
"room.incremental":"true",
"room.expandProjection":"true"]
}
}
}
}
```
由于Room的Dao的基于接口的,它底层使用了apt去生成了XxxDao_Impl类,我们干脆直接定义两个顶层的接口,IRoomDao和IListRoomDao。
```
package dora.cache.holder.dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.RawQuery
import androidx.sqlite.db.SupportSQLiteQuery
interface IRoomDao<T> {
@RawQuery
suspend fun select(query: SupportSQLiteQuery) : T
@Insert
suspend fun insert(model: T) : Long
@Delete
suspend fun delete(model: T) : Int
}
```
```
package dora.cache.holder.dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.RawQuery
import androidx.sqlite.db.SupportSQLiteQuery
interface IListRoomDao<T> {
@RawQuery
suspend fun select(query: SupportSQLiteQuery) : MutableList<T>
@Insert
suspend fun insert(models: MutableList<T>) : MutableList<Long>
@Delete
suspend fun delete(models: MutableList<T>) : Int
}
```
我们也来复习一下Room数据库的使用。
```
@Database(entities = [Book::class], version = 1)
abstract class BookDatabase : RoomDatabase() {
abstract fun bookDao() : BookDao
}
```
先定义一个RoomDatabase的子类。
```
@Entity
@TypeConverters(BookConverter::class)
data class Book(
@PrimaryKey(autoGenerate = true) var id: Long,
val bookName: String,
val bookPageCount: Int,
val bookAuthorInfo: BookAuthorInfo? = null
)
```
```
@Repository(isListMode = false)
class BookRepository(context: Context) : RoomDatabaseCacheRepository<Book, BookDatabase>(context) {
private lateinit var someParams: String
fun initParams(someParams: String) {
this.someParams = someParams
}
override fun query(): Condition {
return WhereBuilder.create().addWhereEqualTo("xx", "xx").toCondition()
}
override fun onLoadFromNetworkObservable(): Observable<Book> {
return get(BookService::class.java)
.getBook()
.compose<BaseResponse<Book>>(RxUtils.exceptionIoTransformer()).map {
it.data
}
}
override fun getRoomDatabase(): BookDatabase {
return Room.databaseBuilder(context, BookDatabase::class.java, "db_book").build()
}
override fun getDaoName(): String {
return "bookDao"
}
}
```
```
@Dao
interface BookDao : IRoomDao<Book> {
@RawQuery(observedEntities = [Book::class])
override suspend fun select(query: SupportSQLiteQuery): Book
@Insert
override suspend fun insert(model: Book): Long
@Delete
override suspend fun delete(model: Book): Int
}
```
另外,我们把Condition也强化一下。
```
fun Condition.having(): String {
return appendClause(" HAVING ", having)
}
fun Condition.orderBy(): String {
return appendClause(" ORDER BY ", orderBy)
}
fun Condition.groupBy(): String {
return appendClause(" GROUP BY ", groupBy)
}
fun Condition.limit(): String {
return appendClause(" LIMIT ", limit)
}
private fun appendClause(name: String, clause: String?) : String {
val s = StringBuilder()
if (!isEmpty(clause)) {
s.append(name)
s.append(clause)
}
return s.toString()
}
/**
* 将查询条件部分转化为sql。
*/
fun Condition.toSQL() : String {
val sb = StringBuilder()
if (selection != "") {
if (!selection.contains("?")) {
sb.append("WHERE").append(" ").append(selection)
} else {
val hasPrefixHolder = selection.startsWith("?")
if (hasPrefixHolder) {
throw IllegalArgumentException("selection can't start with ?")
}
val hasSuffixHolder = selection.endsWith("?")
val specs = selection.split("?")
specs.forEachIndexed {
index, element ->
if (index <= specs.size - 2) {
sb.append(element).append(selectionArgs[index])
}
if (hasSuffixHolder && index == specs.size - 1) {
sb.append(element).append(selectionArgs[index])
}
}
}
sb.append(groupBy() + having() + orderBy() + limit())
}
return sb.toString()
}
```
Condition作为查询条件的协议,通过QueryBuilder和WhereBuilder构建出来的,你一定要好好掌握。不管是内置的ORM框架、Room,又或者是GreenDao,Condition都是通用的。也欢迎各位自己做Orm数据库的整合,并提出宝贵的意见,这样你对框架的理解可以更上一层楼,框架也会因为你而不断完善。瑞思拜!