学习|Android JetPack组件---ORM框架Room的使用

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为6780,预计阅读12分钟

导语

Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。

Room简介

微卡智享

Room 包含 3 个主要组件:

  • 数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。

       使用 @Database 注释的类应满足以下条件:

  1. 是扩展 RoomDatabase 的抽象类。

  2. 在注释中添加与数据库关联的实体列表。

  3. 包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。

  4. 在运行时,您可以通过调用 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"
            )
        }
    }
}



重新运行后显示的结果如下:

再看数据库中的表也多了对应的数据

扫描二维码

获取更多精彩

微卡智享

「 往期文章 」

学习|Android JepPack组件---导航Navigation

实测|A*寻路与JPS寻路同一地图运行效率

实战|JPS跳点寻路实现运行路径规划

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值