学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为6780字,预计阅读12分钟
导语
Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
Room简介
微卡智享
Room 包含 3 个主要组件:
数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 @Database 注释的类应满足以下条件:
是扩展 RoomDatabase 的抽象类。
在注释中添加与数据库关联的实体列表。
包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例。
Entity:表示数据库中的表。
DAO:包含用于访问数据库的方法。
应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。最后,应用使用实体来获取和设置与数据库中的表列相对应的值。
Room 不同组件之间的关系图
Room使用
微卡智享
首先把Room的组件在build.gradle中引入进来
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
如果用Java的话,上面加下进来应该就可以了,因为我用的是Kotlin,直接就加了上面两个,在编译过程中过不去,查了下原因是Kotlin的配置还要再改一下,并且加入kapt,如下:
apply plugin: 'kotlin-kapt'
def room_version = "2.2.5"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
01
简单使用
按上面Room的介绍,我们要创建三个主要组件@Database,@Entity和@Dao
类Entity创建
package com.vaccae.roomdemo.bean
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "Head")
class Product {
@PrimaryKey
@ColumnInfo(name = "Code")
lateinit var code: String
@ColumnInfo(name = "Name")
lateinit var name: String
@ColumnInfo(name = "Unit")
lateinit var unit: String
@ColumnInfo(name = "Price")
var price: Float = 0f
}
这里我们创建了一个Product的类,上面@Entity中tableName就是我们在Sqlite数据库中要生成的表名,@PrimaryKey就是设置的code为主键,@ColumnInfo是用于标识表里的列名,如果这个不写的话就默认是你的属性名。
DAO创建
DAO是访问数据库的方法,我们创建了一个接口实现
@Dao
interface ProductDao {
@Transaction
@Insert
fun add(vararg arr:Product)
@Transaction
@Delete
fun del(vararg arr:Product)
@Transaction
@Update
fun upd(vararg arr:Product)
@Query("select * from Head")
fun getAll():List<Product>
}
@Insert,@Delelte,@Update和@Query这个会点SQL知识的应该都知道这个增删改查,只有@Query的方法后面要改查询语句,并且返回类型也是自己改的。varary里的arr:Product是可变参数,可以列入多个,当然可以再复写一个List<Product>的方法,直接传入列表也可以。
@Transaction就是开启事务,我把增,删,改都加入了事务。
DataBase创建
@Database(entities = [Product::class], version = 1)
abstract class AppDataBase : RoomDatabase() {
abstract fun ProductDao(): ProductDao
}
上面的@Database中加入Product的类,version是数据库的版本号,类继承自RoomDataBase,然后在里面把ProductDao的列入即可
调用方法
class DbUtil {
//创建单例
private var INSTANCE: AppDataBase? = null
fun getDatabase(context: Context): AppDataBase {
if (INSTANCE == null) {
synchronized(lock = AppDataBase::class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDataBase::class.java, "testdb"
)
.allowMainThreadQueries()//允许在主线程查询数据
.addMigrations()//数据库升级时执行
.build()
}
}
}
return INSTANCE!!
}
}
上面就是创建时实现的单例模式,其中里面的allowMainThreadQueries是允许在主线程查询数据,这个我设置上了,主要是做Demo方便,一般这个不建议加上,后面的addMigrations就是数据库升级时要执行的方法,一会儿后面我会说。
里面的"testdb"就是我们起的数据库名,一会儿创建成功后可以看到
这个DBUtil的类我是和上面的DataBase都建在了一个文件里,因为到时候数据库升级只改这一个就可以了。
上面就把Room基本的设置都已经完成了,然后我们主程序写一个测试方法看看,MainActivity中的代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//生成显示产品数据
CreateProduct()
}
private fun CreateProduct() {
//加载AppDataBase
val db = DbUtil().getDatabase(this);
for (i in 1..5) {
val item = Product()
item.code = "0000$i"
item.name = "产品$i"
item.unit = "套"
item.price = 99f
//写入数据
db.ProductDao().add(item)
}
//显示出来
val list = db.ProductDao().getAll()
tvshow.text = ""
list.forEach {
tvshow.append(
it.code + " " + it.name
+ " " + it.unit + " " + it.price + "\r\n"
)
}
}
}
运行后可以看到,我们创建的5条信息也已经显示了出来,创建成功的数据库在虚拟机下data/data/程序包/database/下的三个文件
打开Sqlite数据库后可以看到了这个表名里的数据
这样,简单的Room就已经实现了。
关于Dao的复用及数据库升级
从上面的创建我们可以看到增、删、改基本都是一样的,所以可以用泛型做一个简单的封装,这样别的类要写Dao时可以继承这个基类不用再改增、删、改了。
BaseDao
package com.vaccae.roomdemo.bean
import androidx.room.*
@Dao
interface BaseDao<T> {
@Transaction
@Insert
fun add(vararg arr:T)
@Transaction
@Insert
fun add(arr:ArrayList<T>)
@Transaction
@Update
fun upd(vararg arr:T)
@Transaction
@Update
fun upd(arr:ArrayList<T>)
@Transaction
@Delete
fun del(vararg arr:T)
@Transaction
@Delete
fun del(arr:ArrayList<T>)
}
关于数据库升级
使用过程中,经常会遇到数据库升级的问题,在Room中使本地SQLITE库数据库升级可以用Migration方式,我们直接做一个新的类,对数据进行操作,也直接在原数据库上升级。
01
新建ProductItem类
package com.vaccae.roomdemo.bean
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Query
@Entity(tableName = "Body", primaryKeys = ["Code", "BarCode"])
class ProductItem {
@ColumnInfo(name = "Code")
lateinit var code: String
@ColumnInfo(name = "BarCode")
lateinit var barcode: String
@ColumnInfo(name = "Qty")
var qty = 0
}
@Dao
interface ProductItemDao : BaseDao<ProductItem> {
@Query("select * from Body")
fun getAll(): List<ProductItem>
}
这个类中我把Dao也一起写了进去,直接就是继承自BaseDao,所以只写的Query的查询方法
02
AppDataBase修改
@Database(entities = [Product::class,ProductItem::class], version = 2)
abstract class AppDataBase : RoomDatabase() {
abstract fun ProductDao(): ProductDao
abstract fun ProductItemDao():ProductItemDao
}
上面红框中是修改了的部分
03
DBUtil中修改
class DbUtil {
//数据库升级
var migration1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val sql="CREATE TABLE if not exists Body(Code TEXT NOT NULL ," +
"BarCode TEXT NOT NULL,Qty INTEGER NOT NULL,PRIMARY KEY(Code,BarCode))"
database.execSQL(sql)
}
}
//创建单例
private var INSTANCE: AppDataBase? = null
fun getDatabase(context: Context): AppDataBase {
if (INSTANCE == null) {
synchronized(lock = AppDataBase::class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDataBase::class.java, "testdb"
)
.allowMainThreadQueries()//允许在主线程查询数据
.addMigrations(migration1_2)//数据库升级时执行
.fallbackToDestructiveMigration()
.build()
}
}
}
return INSTANCE!!
}
}
数据库版本升级要执行的语句我们新建了一个Migration,后面的参数是两个INT,分别是旧的版本号和新的版本号
然后在addMigrtions中加入我们创建的这个Migration,不同版本可以写好几个加入进来,系统会根据当前版本找到对应的方案进行数据库升级
为了防止出现升级失败导致应用程序Crash的情况,我们可以在创建数据库时加入fallbackToDestructiveMigration()方法。该方法能够在出现升级异常时,重新创建数据库表。虽然应用程序不会Crash,但由于数据表被重新创建,所有的数据也将会丢失。
04
MainActivity调用修改
package com.vaccae.roomdemo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.vaccae.roomdemo.bean.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//生成显示产品数据
CreateProduct()
//生成明细数据
CreateProductItem()
}
private fun CreateProductItem() {
//定义明细列表
val itemlist = ArrayList<ProductItem>()
//加载AppDataBase
val db = DbUtil().getDatabase(this);
//显示所有Product的明细
val list = db.ProductDao().getAll()
list.forEach {
for (i in 1..3) {
val item = ProductItem()
item.code = it.code
item.barcode = it.code + i.toString()
item.qty = 1
itemlist.add(item)
}
}
db.ProductItemDao().add(itemlist)
//显示明细
val getlist= db.ProductItemDao().getAll()
tvshow.text = ""
getlist.forEach {
tvshow.append(
it.code + " " + it.barcode
+ " " + it.qty + "\r\n"
)
}
}
private fun CreateProduct() {
//加载AppDataBase
val db = DbUtil().getDatabase(this);
for (i in 1..5) {
val item = Product()
item.code = "0000$i"
item.name = "产品$i"
item.unit = "套"
item.price = 99f
//写入数据
db.ProductDao().add(item)
}
//显示出来
val list = db.ProductDao().getAll()
tvshow.text = ""
list.forEach {
tvshow.append(
it.code + " " + it.name
+ " " + it.unit + " " + it.price + "\r\n"
)
}
}
}
重新运行后显示的结果如下:
再看数据库中的表也多了对应的数据
完
扫描二维码
获取更多精彩
微卡智享
「 往期文章 」