声明 : https://www.jianshu.com/p/714062a9af75
目录
简介
原理
使用方法1,基本使用
2, Room 与 LiveData,ViewModel 的结合使用
3,数据库升级
简介:
Android 存储数据的方式:SharePerference,文件存储,SQLite 数据库存储,第三方数据库.Google 在 SQLite 基础上封装了了轻量级的数据库 Room,访问数据库更稳更快.
Room持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同事,获享更强健的数据库访问机制,该库可以帮助您运行应用的设备上创建应用数据的缓存.此缓存应充当应用的单一可信来源,使用户能够在应用中查看关键的信息的一致副本,无论用户是否具有互联网链接.
原理:
- Data Access Objects : 数据访问集
- Get Entities from db : 从数据库获取实体类
- Persist changes back to db : 数据持久化后返回数据库
- get/set field values : 获取字段的值
- Dao : 包含用户访问数据库的方法
几个关键的注解:
- @Entity 对应数据库中的一张表,可以设置表名,若不指定,则为类名
- @PrimaryKey 指定表的主键
- @ColumnInfo 指定列的名字,若不指定,则为属性名字
- @Ignore 忽略字段或者方法
- @Dao (data access object) 接口文件或者抽象类,以便对 Entity 进行访问
- @Database 告诉系统这是 Room 数据库对象.enterties 属性用于指定有哪些表,多张表,用逗号隔开,version 用于指定数据库的版本号.
使用方法:
官网连接 : https://developer.android.google.cn/topic/libraries/architecture/room 查看引用的最新版本
1.build.gradle
apply plugin: 'kotlin-kapt'
.......
//room
implementation "androidx.room:room-runtime:2.3.0"
annotationProcessor "androidx.room:room-compiler:2.3.0"
//Room的Kotlin扩展和对协程的支持
kapt "androidx.room:room-compiler:2.3.0"
因为我用 kotlin 写的项目,所以要加第三个引用~~~否则会报错哦.
java ,不需要~~
2.创建一个学生的 Entity,即创建一张学生的表
@Entity(tableName = "table_student")
data class Student(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "s_id", typeAffinity = ColumnInfo.INTEGER)
var id: Int,
@ColumnInfo(name = "s_name", typeAffinity = ColumnInfo.TEXT)
var name: String,
@ColumnInfo(name = "s_sex", typeAffinity = ColumnInfo.TEXT)
var sex: String
) {
/***
* 由于 Room 只能识别和使用一个构造器,如果想使用多个构造器,使用 @Ignore 标签,忽略构造器
*
*/
@Ignore
constructor(name: String, sex: String) : this(0, name, sex) {
this.name = name
this.sex = sex
}
}
3.学生的Dao 接口文件
@Dao
interface StudentDao {
@Insert
fun insertSudent(stdent: Student)
@Delete
fun deleteStudent(stdent: Student): Int
@Update
fun updataStudent(stdent: Student): Int
@Query("select * from table_student")
fun getStudentList(): MutableList
@Query("select * from table_student where s_id =:id")
fun getStudent(id: Int): Student
}
4.创建数据库
@Database(entities = [Student::class], version = 1)
abstract class StuentDataBase : RoomDatabase() {
companion object {
@Synchronized
fun getInstance(context: Context): StuentDataBase {
var stuentDataBase: StuentDataBase? = null
val DATABASE_NAME = "student_db"
stuentDataBase =
Room.databaseBuilder(context, StuentDataBase::class.java, DATABASE_NAME).build()
return stuentDataBase
}
}
abstract fun studentDao(): StudentDao
}
5.对数据库进行增删改查
⚠️⚠️⚠️不能再 UI线程操作增删改查,必须在工作线程中⚠️⚠️⚠️
看完成的代码吧,我是通过 AsycTask 的方式实现的,当然了,其他方式还有很多种~~~
class TestActivity : AppCompatActivity() {
lateinit var stuentDataBase: StuentDataBase
lateinit var studentList: MutableList
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
stuentDataBase = StuentDataBase.getInstance(this)
}
fun onclick(view: View) {
when (view.id) {
R.id.button1 -> {//插入数据
InsertStudentTask(Student("张三", "男")).execute()
}
R.id.button -> {//删除数据
DeleteStudentTask(Student(2, "张三", "男")).execute()
}
R.id.button2 -> {//更新数据
UpdateStudentTask(Student(1, "张三", "女")).execute()
}
R.id.button3 -> {//查询数据
QueryStudentTask().execute()
}
}
}
inner class InsertStudentTask(var student: Student) : AsyncTask() {
override fun doInBackground(vararg p0: Void?): Void? {
stuentDataBase.studentDao().insertSudent(student)
return null
}
override fun onPostExecute(unused: Void?) {
super.onPostExecute(unused)
}
}
inner class DeleteStudentTask(var student: Student) : AsyncTask() {
override fun doInBackground(vararg p0: Void?): Void? {
var deleteStudent = stuentDataBase.studentDao().deleteStudent(student)
Log.e("TestActivity", "删除成功$deleteStudent")
return null
}
override fun onPostExecute(unused: Void?) {
super.onPostExecute(unused)
}
}
inner class UpdateStudentTask(var student: Student) : AsyncTask() {
override fun doInBackground(vararg p0: Void?): Void? {
var updataStudent = stuentDataBase.studentDao().updataStudent(student)
Log.e("TestActivity", "更新成功$updataStudent")
return null
}
override fun onPostExecute(unused: Void?) {
super.onPostExecute(unused)
}
}
inner class QueryStudentTask : AsyncTask() {
override fun doInBackground(vararg p0: Void?): Void? {
studentList = stuentDataBase.studentDao().getStudentList()
return null
}
override fun onPostExecute(unused: Void?) {
super.onPostExecute(unused)
for (student in studentList) {
Log.e("TestActivity", student.toString())
}
}
}
}
看运行截图
好了,到此为止,简单的使用已经完成了~~~~
Room 与 LiveData,ViewModel 的结合使用
我们知道LiveData 通常和 ViewModel 一起使用.ViewModel 是存放数据的,因此我们可以将数据库的初始化放在 Viewm 中.当我们执行增,删,改的操作的时候,我们可以用过 LiveData 组件通知我们数据的变化,实现数据自动更新.
1,修改StudentDao
@Query("select * from table_student")
fun getStudentList(): LiveData>
2,创建 StudentViewModel
数据库的初始化需要 Context,我们不宜传入 Context,会导致内存泄漏,但是我们可以使用子类AndroidViewModel,其构造器中含有 Application 的参数,Application 作为 Context 的子类,可以用于数据库的初始化.
public class StudentViewModel extends AndroidViewModel {
private StuentDataBase stuentDataBase;
public LiveData> getLivaDataStudent() {
return livaDataStudent;
}
private LiveData> livaDataStudent;
public StudentViewModel(@NonNull @NotNull Application application) {
super(application);
stuentDataBase = StuentDataBase.Companion.getInstance(application);
livaDataStudent = stuentDataBase.studentDao().getStudentList();
}
}
3,在TestActivity实例化 StudentViewModel,监听数据变化
class TestActivity : AppCompatActivity() {
lateinit var stuentDataBase: StuentDataBase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
stuentDataBase = StuentDataBase.getInstance(this)
var studentViewModel = ViewModelProvider(this).get(StudentViewModel::class.java)
studentViewModel.livaDataStudent.observe(this,
{
for (student in it) {
Log.e("TestActivity", student.toString())
}
})
}
}
当在执行插入,修改,删除的操作的时候,会自动执行查询才做,检测结果.
数据库升级
随着业务的增加,我们会对数据库做一些调整,比如在表中新加一个字段.Android 提供了一个类为 Migration 的类来进行升级.Migration有两个参数,startVersion 和 endVersion.
//从 1 到 2
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
//执行相关的操作
}
}
//从 2 到 3
val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
//执行相关的操作
}
}
//.........
以此类推,然后通过addMigrations()添加升级,
Room.databaseBuilder(context.applicationContext,StuentDataBase::class.java,DATABASE_NAME)
.addMigrations(MIGRATION_1_2,MIGRATION_2_3,....)
.build()
同事,也要修改数据库的版本号
@Database(entities = [Student::class], version = 2)
⚠️如果当前数据库版本的为 1,而当前要安装的版本为 3,那么 Room 会判断当前有没有从 1 升到 3 的方案,如果有,则执行 Migration(1 , 3) 的方案,如果没有,则会执行 Migration(1 , 2),再从Migration(2 , 3).
⚠️如果数据库版本升到 4,但又没有相应 Migration ,为了防止异常崩溃,我们可以再创建数据库的时候,加入fallbackToDestructiveMigration()方法,该方法在出现异常的时候,重新创建数据库表,虽然不会崩溃,但是数据会丢失哦.
Room.databaseBuilder(context.applicationContext,StuentDataBase::class.java,DATABASE_NAME)
.fallbackToDestructiveMigration()
.addMigrations(MIGRATION_1_2,MIGRATION_2_3,....)
.build()
举例说明: 上面的例子中,学生的表中,新加一个 age 字段
1.Student
@Entity(tableName = "table_student")
data class Student(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "s_id", typeAffinity = ColumnInfo.INTEGER)
var id: Int,
@ColumnInfo(name = "s_name", typeAffinity = ColumnInfo.TEXT)
var name: String,
@ColumnInfo(name = "s_sex", typeAffinity = ColumnInfo.TEXT)
var sex: String,
@ColumnInfo(name = "s_age")
var age: Int,
) {
/***
* 由于 Room 只能识别和使用一个构造器,如果想使用多个构造器,使用 @Ignore 标签,忽略构造器
*
*/
@Ignore
constructor(name: String, sex: String) : this(0, name, sex, 0) {
this.name = name
this.sex = sex
}
}
2 编写Migration类,并执行升级
@Database(entities = [Student::class], version = 2)
abstract class StuentDataBase : RoomDatabase() {
companion object {
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
//创建一个新表
database.execSQL(
"create table table_student2(s_id INTEGER PRIMARY KEY not null,s_name TEXT not null,s_sex TEXT not null,s_age INTEGER not null default 0 )"
)
//复制数据
database.execSQL("insert into table_student2(s_id,s_name,s_sex) select s_id,s_name,s_sex from table_student")
//删除旧的表
database.execSQL("drop table table_student")
//重新命名数据表的名字
database.execSQL("alter table table_student2 rename to table_student")
}
}
private var stuentDataBase: StuentDataBase? = null
private const val DATABASE_NAME = "student_db"
@Synchronized
fun getInstance(context: Context): StuentDataBase {
if (stuentDataBase == null) {
stuentDataBase = Room.databaseBuilder(
context.applicationContext,
StuentDataBase::class.java,
DATABASE_NAME
)
.addMigrations(MIGRATION_1_2)
.build()
}
return stuentDataBase as StuentDataBase
}
}
abstract fun studentDao(): StudentDao
}
3,TestActivity还是不变的~,看一下运行截图
我们更新了一条数据,更新是没问题的,age 字段也打印出来啦,默认是 0.