Android Jetpack 之 Room数据库的使用

Room的小试牛刀

  • 一、前言
  • 二、为什么要使用Room
  • 二、使用步骤
    • 1、添加依赖
    • 2、数据库表Entity的创建
    • 3、数据库Database的创建
    • 4、数据库增删改查操作接口的定义
    • 5、使用Room操作数据
  • 三、小结

一、前言

Android Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。而这篇文章中的主角Room正好是Jetpack 的重要库之一,Room数据库在这近几年也是Google极力推荐使用的数据库。
说到这个主角,Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的用例是缓存相关数据。这样,当设备无法访问网络时,用户仍可在离线状态下浏览相应内容。设备重新连接到网络后,用户发起的所有内容更改都会同步到服务器。
在下面的学习中,我将以Kotlin的开发环境中进行,毕竟要迎合Google大佬官方开发语言的推荐,话不多说,我们直接展开说下这个数据库是怎么使用的。


Android Jetpack 之 Room数据库的使用_第1张图片


二、为什么要使用Room

想必原生的Android数据库SQLite打击都不陌生,而本篇文章的主角Room其实就只是对原生的SQLite API进行了一层封装,使得更加流畅的访问数据库。另外它和常规的ORM框架一样,通过添加编译期注解来进行表和字段的配置,其包含了三个主要组件:

1、Databasee:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点;使用 @Database 注释的类应满足以下条件:是扩展 RoomDatabase 的抽象类。在注释中添加与数据库关联的实体列表。包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例。

2、Entity:表示数据库中的表,通过@Entity标识。

3、DAO:包含用于访问数据库的方法,通过@Dao标识。

二、使用步骤

1、添加依赖

Kotlin环境依赖如下:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
  }

android {
  defaultConfig {
  ...
    kapt {
          arguments {
             arg("room.schemaLocation", "$projectDir/schemas")
           }
       }
    }
}

dependencies {
  def room_version = "2.2.6"
  implementation "androidx.room:room-runtime:$room_version"
  kapt "androidx.room:room-compiler:$room_version"
  // optional - Kotlin Extensions and Coroutines support for Room
  implementation "androidx.room:room-ktx:$room_version"
  // optional - Test helpers
  testImplementation "androidx.room:room-testing:$room_version"
}

2、数据库表Entity的创建

通过@Entity注解标识实体类,其中tableName 表示明确表名,如果不明确表名数据库指定当前实体作为表名,@PrimaryKey注解标识主键,是唯一的值,值不能为空,我这里默认指定为0,autoGenerate = true表示主键自增; @ColumnInfo注解标识表中列信息,name =指定列中对应数据库字段名称,不指定时默认为自定义的参数名称:

@Entity(tableName = "student_info")
data class StudentEntity(
    @ColumnInfo(name = "stu_name")
    val name: String?,
    @ColumnInfo(name = "stu_sex")
    val sex: String?,
    @ColumnInfo(name = "stu_skill")
    val skill:String?,
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0
)

3、数据库Database的创建

创建数据库需要继承RoomDatabase的抽象类,并且通过@Database标识这个类,@Database中里面有几个参数可以展开说一下,entities表示要添加进来的数据库表,这里是以数组的形式添加,后续有多个表可以用逗号隔开添加进来,version表示这个数据库的版本,版本迁移时会使用到,exportSchema表示导出为文件模式,默认为true,除了添加表映射的类以及和数据库版本外,还要添加exportSchema = false否则会报警告。这个类里面还定义了获取增删改查操作接口的方法StudentDAO,下面还涉及了版本迁移的方法,当数据库表Entity中某些字段增加或减少就需要调用数据库迁移的方法,此外,打比方原来的版本(version)是1,就要改成2,下一次字段变更时,版本以此类推的增加。想要保留数据的情况下做版本迁移就需要调用addMigrations()方法,这个非常重要。

@Database(entities = [StudentEntity::class], version = 3, exportSchema = false)
abstract class StudentDB : RoomDatabase() {

    abstract fun getStudentDao(): StudentDAO

    companion object {
        //版本迁移:2->3(这个是增加字段下的迁移)
        private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE student_info ADD COLUMN stu_skill TEXT") //在数据库表student_info中添加了一个字段stu_skill
            }
        }

        //版本迁移3->4(这个是删减字段下的迁移,过程较复杂),整个步骤如下:
        private val MIGRATION_3_4: Migration = object : Migration(3, 4) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //1.在旧表的前提下先创建一个删减掉某个字段的新表(我这里包含字段:id,stu_name,stu_sex),去除stu_skill字段;
                database.execSQL("CREATE TABLE student_info_temp (stu_name TEXT,stu_sex TEXT,id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL) ")
                // 2.然后将旧表student_info的数据转移到新表student_info_temp中(旧表和新表的字段要保持一致)
                database.execSQL("INSERT INTO student_info_temp (stu_name,stu_sex,id) SELECT stu_name,stu_sex,id FROM student_info")
                // 3.将旧表student_info删除.
                database.execSQL("DROP TABLE student_info")
                //4.将新建的表名命名student_info_temp成旧表的名student_info
                database.execSQL("ALTER TABLE student_info_temp RENAME TO student_info")
            }

        }
        //双重检测锁单例初始化StudentDB
        val INSATANCE: StudentDB by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Room
                .databaseBuilder(App.appContext(), StudentDB::class.java, "StudentAllInfo.db")
                .addMigrations(MIGRATION_2_3)//这个方法是在保留数据的前提下做版本迁移,fallbackToDestructiveMigration()是将原有数据清空在做版本迁移
                .build()
        }

    }

}

4、数据库增删改查操作接口的定义

定义一个数据操作接口,使用@Dao注解标识,Room中提供了@Insert@Delete@Update@Query@RawQuery等注解,通过注解标识实现对应的增删改查的方法,函数中suspend 表示函数挂起,在协程中使用需要此关键字修饰,挂起函数挂起协程,并不会阻塞协程所在的线程:

@Dao
interface StudentDAO {
    //插入多个数据
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(students: MutableList<StudentEntity>)

    //展示所有数据
    @Query("SELECT * FROM student_info")
    fun queryAll(): LiveData<MutableList<StudentEntity>>

    //删除表中所有数据
    @Query("DELETE FROM student_info")
    suspend fun deleteAll()

    //通过ID修改数据
    @Query("UPDATE student_info SET stu_name=:name,stu_sex=:sex,stu_skill=:skill WHERE id=:id")
    suspend fun updateData(id: Long, name: String, sex: String, skill: String)

    //根据Id删除数据
    @Query("DELETE FROM STUDENT_INFO WHERE id=:id")
    suspend fun deleteById(id: Long)

    //根据姓名删除数据
    @Query("DELETE FROM STUDENT_INFO WHERE stu_name=:name")
    suspend fun deleteByName(name: String)

    //根据性别删除数据
    @Query("DELETE FROM STUDENT_INFO WHERE stu_sex=:sex")
    suspend fun deleteBySex(sex: String)
}

这里插个题外话,本想在这个操作接口中使用Rxjava实现对数据库增删改查的操作,但定义好方法,Build的时候出现一系列错误,通过查找文档,Google搜索问题未果,只能放弃(PS:哪位大兄弟如果有此问题解决方式或者文章可以麻烦推送给我),最终我这里使用LiveData搭配Room一起使用,实现数据实时更新的监听效果,在操作数据时使用Kotlin的协程Coroutine对LiveData不太熟悉的可以自行查询官方文档。

5、使用Room操作数据

初始化Database,这里我使用了Koin依赖注入,关于这个框架我下次出一个使用教程:

class App : Application(){
    override fun onCreate() {
          super.onCreate()
        //初始化Koin依赖注入框架
        initKoin()
    }
    //数据库管理
    private val dbMode = module {
        single { StudentDB.INSATANCE }//初始化Database
        single { get<StudentDB>().getStudentDao() }//获取StudentDAO数据库操作类
    }
    
     private fun initKoin() {
        startKoin {
            androidContext(this@App)
            modules(dbMode)
        }
    }
} 

在Activity中使用协程lifecycleScope.launch通过线程切换方式操作数据,实现与Rxjava相同的效果,launch中提供了线程方式:DefaultMainUnconfinedIO,默认是在Main中执行,也就是主线程:

class RoomDataBaseLearnActivity : AppCompatActivity() {
    //通过依赖注入方式获取数据库操作类
    private val studentDao: StudentDAO by inject()
    
    private var job: Job? = null

    private var vb: ActivityRoomDataBaseLearnBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        vb = ActivityRoomDataBaseLearnBinding.inflate(layoutInflater)
        vb?.apply {
            setContentView(root)
            //插入多条数据
            btnInsertAll.setOnClickListener {
                insertMultiData()
            }
            //修改某条数据
            btnUpdateData.setOnClickListener {
                updateData()
            }
            //删除所有数据
            btnDeleteAll.setOnClickListener {
                deleteAllData()
            }
            //根据ID删除数据
            btnDeleteByid.setOnClickListener {
                delDataById()
            }
            //根据姓名删除数据
            btnDeleteByname.setOnClickListener {
                delDataByName()
            }
             //根据性别删除数据
            btnDeleteBysex.setOnClickListener {
                delDataBySex()
            }
            //实时查询(刷新)数据
            refreshData()
        }
    }

    private fun delDataBySex() {
        job = lifecycleScope.launch(Dispatchers.IO) {
            studentDao.deleteBySex("男")
            loadThreadLog("根据性别删除")
        }

    }

    private fun delDataByName() {
        job = lifecycleScope.launch(Dispatchers.IO) {
            studentDao.deleteByName("张三丰")
            loadThreadLog("根据姓名删除")
        }
    }

    private fun delDataById() {
        job = lifecycleScope.launch(Dispatchers.IO) {
            studentDao.deleteById(1)
            studentDao.deleteById(2)
            loadThreadLog("根据ID删除")
        }

    }

    private fun refreshData() {
        studentDao.queryAll().observe(this@RoomDataBaseLearnActivity, {
            job = lifecycleScope.launch(Dispatchers.IO) {//子线程IO处理数据库耗时操作
                loadThreadLog("查询刷新数据---->外")
                val sb = StringBuilder()
                it.forEach {
                    sb.append(it.id).append(" ").append(it.name).append(" ").append(it.sex).append(" ").append(it.skill)
                        .append("\n")
                }
                withContext(Dispatchers.Main) {//切换主线程显示UI
                    vb!!.tvRoom.text = sb.toString()
                    loadThreadLog("查询刷新数据---->内")
                }
            }
        })
    }

    private fun deleteAllData() {
        job = lifecycleScope.launch(Dispatchers.IO) {
            studentDao.deleteAll()
            loadThreadLog("删除数据")
        }
    }

    private fun updateData() {
        job = lifecycleScope.launch(Dispatchers.IO) {
            studentDao.updateData(2, "武松", "男","力大无穷")
            studentDao.updateData(8, "扈三娘", "女","彪悍")
            studentDao.updateData(11, "姜子牙", "男","周易卜卦")
            studentDao.updateData(13, "妲己", "女","迷惑")
            loadThreadLog("修改数据")
        }
    }

    private fun insertMultiData() {
        job = lifecycleScope.launch(Dispatchers.IO) {
            studentDao.insertAll(DataManager.studentData)
            loadThreadLog("插入数据")
        }
    }

    private fun loadThreadLog(method: String) {
        Log.e(
            "协程线程---->",
            "协程执行$this ----> $method   线程id:${Thread.currentThread().id} 当前是否为主线程:${isMainThread()}"
        )
    }
    override fun onDestroy() {
        super.onDestroy()
        if (vb != null)
            vb = null
        job?.cancel()
        job = null
    }
}

实现的数据操作效果如下图所示:
Android Jetpack 之 Room数据库的使用_第2张图片

三、小结

Room作为Google官方推荐使用的数据库,相比较某些优秀数据库框架来说,不用过于担心某天库会停止维护(参考GreenDao现在的状况),访问数据库非常流畅,并且提供了与常规的ORM框架一样,通过添加编译期注解来进行表和字段的配置,譬如@Database、@Dao、@Entity、@Query、@Insert、@Update、@Detele等的注解,可以使用简单代码实现相比以前SQLite更复杂的代码的效果。总而言之, Room算得上是一个优秀的数据库,最后希望这篇文章不仅仅是自己作为学习者记录学习的过程,更希望能帮助刚入门这个数据库的人小试牛刀,最后附上自己的GitHub学习项目,这小项目旨在自己作为kotlin开发语言下展开对以前乃至现在热门的框架的学习,这段时间自己会不留余力去更新其他框架。

我的Github项目:KotlinAppFramework:此库针对Kotlin开发环境下对Android各大框架的简单使用,涉及到的框架有:Glide,Retrofit,Rxjava,ARouter,EventBus等,以促进基础学习的过程。

你可能感兴趣的:(kotlin,数据库,Android,数据库,android,kotlin,android,studio,github)