在APP的迭代过程中,会遇到给数据库表增加字段或者增加表的的需求,这时候就涉及到数据库的Migration。本节我们将来介绍一下Room的Migration。

上节我们用数据库建了一张play_list_tags表实现了歌单标签的增删改查功能。

本节我们建一张net_cache表,用来缓存一些主要界面的网络数据内容,这样当网络异常时可以用数据库中的缓存内容填充界面,不至于整个空白一片,优化用户的使用体验。

新建 Entity

我们新建一个NetCache,代码如下:

@Entity(tableName = "net_cache")

data class NetCache (

@PrimaryKey(autoGenerate = false)

val type: Int,

val content: String

)

NetCache 比较简单,表名为net_cache; 表包含两个字段,type表示网络请求数据的类型,content表示请求到的数据的JSON字符串。

@Dao

interface NetCacheDao {

@Query("SELECT * FROM net_cache WHERE type = :type")

suspend fun getNetCache(type: Int): NetCache

@Insert(onConflict = OnConflictStrategy.REPLACE)

suspend fun insertNetCache(cache: NetCache)

@Delete

suspend fun deleteNetCache(cache: NetCache)

@Update(onConflict = OnConflictStrategy.REPLACE)

suspend fun updateNetCache(cache: NetCache)

}

新建 Dao

接下来我们新建一个 NetCacheDao。

@Dao

interface NetCacheDao {

@Query("SELECT * FROM net_cache WHERE type = :type")

suspend fun getNetCache(type: Int): NetCache

@Insert(onConflict = OnConflictStrategy.REPLACE)

suspend fun insertNetCache(cache: NetCache)

@Delete

suspend fun deleteNetCache(cache: NetCache)

@Update(onConflict = OnConflictStrategy.REPLACE)

suspend fun updateNetCache(cache: NetCache)

}

其他的方法比较简单,重点说一下getNetCache方法,我们看到方法接收一个type参数,然后这个参数替换@Query的SQL语句中SELECT * FROM net_cache WHERE type = :type的占位符:type.

修改 Database

我们接下来修改下MusicDatabase

@Database(version = 1, entities = [PlayListTagResponse.PlayListTag::class, NetCache::class], exportSchema = false)

abstract class MusicDatabase : RoomDatabase() {

// ...

abstract fun netCacheDao(): NetCacheDao

}

在@Database的注解中的entities元素中增加NetCache::class

添加一个netCacheDao方法,这个方法返回NetCacheDao

修改 HomeViewModel

发现页有两个请求,一个请求Banner,一个请求其他Block信息。

两个请求

修改的主要代码如下:

// 1

class HomeViewModel(application: Application) : AndroidViewModel(application) {

// 2

private val netRepository : NetCacheRepository

init {

val netCacheDao = MusicDatabase.getInstance(application).netCacheDao()

netRepository = NetCacheRepository(netCacheDao)

fetchData()

}

// 请求数据

fun fetchData() {

try {

viewModelScope.launch(Dispatchers.IO) {

val bannerJob = async { HomeRepository.getHomeBanner() }

val blocksJob = async { HomeRepository.getHomeBlocks() }

var bannerResponse = bannerJob.await()

var blocksResponse = blocksJob.await()

if (bannerResponse.code != NetErrorType.UnSpecified && blocksResponse.code != NetErrorType.UnSpecified) {

withContext(Dispatchers.Main) {

_homeLiveData.value = Pair(bannerResponse.banners, blocksResponse.data)

}

// 3

updateBannerCache(bannerResponse)

updateBlockCache(blocksResponse)

return@launch

}

// 4

if (bannerResponse.code == NetErrorType.UnSpecified) {

val cache = getBannerCache()

cache?.let {

bannerResponse = it

}

}

if (blocksResponse.code == NetErrorType.UnSpecified) {

val cache = getBlockCache()

cache?.let {

blocksResponse = it

}

}

withContext(Dispatchers.Main) {

_homeLiveData.value = Pair(bannerResponse.banners, blocksResponse.data)

}

}

} catch (e: Exception) {

}

}

/* 存入Banner缓存 */

private suspend fun updateBannerCache(banners: BannerResponse) {

val jsonAdapter = Moshi.Builder().build().adapter(BannerResponse::class.java)

val jsonStr = jsonAdapter.toJson(banners)

netRepository.insertNetCache(NetCache(NetCacheType.HomeBanner, jsonStr))

}

/* 存入Blocks缓存 */

private suspend fun updateBlockCache(blocks: HomeResponse) {

val jsonAdapter = Moshi.Builder().build().adapter(HomeResponse::class.java)

val jsonStr = jsonAdapter.toJson(blocks)

netRepository.insertNetCache(NetCache(NetCacheType.Discovery, jsonStr))

}

/* 获取Banner缓存 */

private suspend fun getBannerCache(): BannerResponse? {

val cacheStr = netRepository.getNetCache(NetCacheType.HomeBanner) ?: return null

val jsonAdapter = Moshi.Builder().build().adapter(BannerResponse::class.java)

return jsonAdapter.fromJson(cacheStr.content)

}

/* 获取Block缓存 */

private suspend fun getBlockCache(): HomeResponse? {

val cacheStr = netRepository.getNetCache(NetCacheType.Discovery) ?: return null

val jsonAdapter = Moshi.Builder().build().adapter(HomeResponse::class.java)

return jsonAdapter.fromJson(cacheStr.content)

}

}

主要修改有:

为了获取到Application将ViewModel改为AndroidViewModel,

定义一个NetCacheRepository属性来操作数据库

获取到网络数据后存入数据库中,见updateBannerCache和updateBlockCache方法

如果没有获取到网络数据,就从数据库中获取。见getBannerCache和getBlockCache方法

到目前为止,代码已经修改完毕,运行程序,程序报错了。

报错内容为Room cannot vertify the data integrity. Looks like you've changed schema but forgot to update the version number.

错误

这是错误是因为我们添加了表,需要进行Migration

Migration

将 MusicDatabase的Version修改为2

@Database(version = 2, entities = [PlayListTagResponse.PlayListTag::class, NetCache::class], exportSchema = false)

新建一个Migration

private class Migration1To2: Migration(1, 2) {

override fun migrate(database: SupportSQLiteDatabase) {

database.execSQL("CREATE TABLE IF NOT EXISTS net_cache" +

"('type' INTEGER NOT NULL PRIMARY KEY, 'content' TEXT NOT NULL)")

}

}

新建一个Migration1To2,继承自Migration,构造函数的1,2代表从版本1前移到版本2.

在migrate方法中执行建表操作,CREATE TABLE IF NOT EXISTS net_cache ('type' INTEGER NOT NULL PRIMARY KEY, 'content' TEXT NOT NULL)。

初始化MusicDatabase的时候调用addMigrations方法

val instance = Room.databaseBuilder(application, MusicDatabase::class.java, "music_database")

.addCallback(MusicDatabaseCallBack())

.addMigrations(Migration1To2())

.build()

运行程序,现在一切正常了。通过Migration我们就实现了页面网络请求的数据库缓存。

网络请求缓存

java migration_JetPack知识点实战系列九:Room数据库Migration相关推荐

  1. Java秒杀系统实战系列~数据库级别Sql的优化与代码的调整

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第十三篇,从本篇文章开始我们将进入"秒杀代码优化"环节,本文将首先从数据库级别Sql的优化入手,结合调整秒杀 ...

  2. Java秒杀系统实战系列~RabbitMQ死信队列处理超时未支付的订单(转)

    转自: https://juejin.cn/post/6844903903130042376 文末有源代码,非常棒 摘要: 本篇博文是"Java秒杀系统实战系列文章"的第十篇,本篇 ...

  3. Java秒杀系统实战系列~分布式唯一ID生成订单编号

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第七篇,在本博文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们 ...

  4. Java秒杀系统实战系列~构建SpringBoot多模块项目

    摘要:本篇博文是"Java秒杀系统实战系列文章"的第二篇,主要分享介绍如何采用IDEA,基于SpringBoot+SpringMVC+Mybatis+分布式中间件构建一个多模块的项 ...

  5. Java秒杀系统实战系列~JMeter压力测试重现秒杀场景中超卖等问题

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第十二篇,本篇博文我们将借助压力测试工具Jmeter重现秒杀场景(高并发场景)下出现的各种典型的问题,其中最为经典的当属&quo ...

  6. Java秒杀系统实战系列~基于Redisson的分布式锁优化秒杀逻辑

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第十五篇,本文我们将借助综合中间件Redisson优化"秒杀系统中秒杀的核心业务逻辑",解决Redis的原子 ...

  7. Java秒杀系统实战系列~定时任务补充处理超时未支付的订单

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第十一篇,本篇博文我们将借助定时任务调度组件来辅助"失效超时未支付的订单记录"的处理,用以解决上篇博文中采用 ...

  8. Java秒杀系统实战系列~商品秒杀代码实战

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第六篇,本篇博文我们将进入整个秒杀系统核心功能模块的代码开发,即"商品秒杀"功能模块的代码实战. 内容: & ...

  9. JAVA面试常考系列九

    转载自 JAVA面试常考系列九 题目一 RMI架构层的结构是如何组成的? RMI体系结构由三层组成,分别是: 存根和骨架层(Stub and Skeleton Layer) 远程引用层(Remote ...

  10. Java基础复习笔记系列 九 网络编程

    Java基础复习笔记系列之 网络编程 学习资料参考: 1.http://www.icoolxue.com/ 2. 1.网络编程的基础概念. TCP/IP协议:Socket编程:IP地址. 中国和美国之 ...

最新文章

  1. oracle循环语句loop,oracle循环语句loop
  2. [LeetCode]Gray Code
  3. jquery checkbox attr区别prop
  4. Linux驱动中获取系统时间
  5. 机器找不到 libcudnn.so.6
  6. java输出到文本_java怎么把运行结果写到一个自动生成的文本里?。能给个例子么,详细点...
  7. 云服务器更换系统要钱吗,云服务器可以更换系统吗
  8. 爬虫实例十三 教你怎么用爬虫一次给女朋友拿下28万张情侣头像
  9. php 获取小数精度,php小数精度问题
  10. Android开发视频教程汇总
  11. 从电信的广告学习情景与文案的搭配
  12. python中 {0:2.2f}与{1:2.2f}的区别
  13. RGB转换HSL,HSV及切割车牌
  14. 关于 Kubernetes集群中仪表盘(dashboardKuboard)安装的一些笔记
  15. pyinstaller打包exe加入版本和版权信息
  16. LabVIEW基础(1)
  17. CMake的一些细节
  18. 如何从iCloud共享iWork文档
  19. paypay+thinkphp开发接入网站
  20. 狐狸抓兔子(实验楼的练习)

热门文章

  1. stm32视频教程分享:心率检测仪的设计与实现
  2. 【EduCoder答案】HTML——表单类的标签
  3. 利用线性回归预测波士顿房价
  4. Gym 100015B Ball Painting
  5. Ubuntu18.04系统下装CUDA9.0
  6. 小程序获取附近IBeacon设备
  7. 【开源编码分享】Python古三式∶太乙神数丶奇门遁甲丶大六壬
  8. 美通社企业新闻汇总 | 2019.3.7 | 百胜中国在上海设创新中心;折叠手机2019年预计仅占智能手机市场渗透率0.1%...
  9. 【对比Java学Kotlin】类型别名
  10. 并发编程之四:并发之共享问题、线程安全、synchronized关键字