导语
Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
Room简介
Room 包含 3 个主要组件:
使用 @Database 注释的类应满足以下条件:
应用使用 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
package com.vaccae.roomdemo.bean
import androidx.room.ColumnInfoimport androidx.room.Entityimport 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是访问数据库的方法,我们创建了一个接口实现
@Daointerface 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(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时可以继承这个基类不用再改增、删、改了。
BaseDao
package com.vaccae.roomdemo.bean
import androidx.room.*
@Daointerface 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.ColumnInfoimport androidx.room.Daoimport androidx.room.Entityimport 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}
@Daointerface 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.AppCompatActivityimport android.os.Bundleimport 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" ) } }}
重新运行后显示的结果如下:
再看数据库中的表也多了对应的数据
完