Jetpack 之 Room 小白入手

声明 : https://www.jianshu.com/p/714062a9af75
目录

简介
原理
使用方法

1,基本使用
2, Room 与 LiveData,ViewModel 的结合使用
3,数据库升级

简介:

   Android 存储数据的方式:SharePerference,文件存储,SQLite 数据库存储,第三方数据库.Google 在 SQLite 基础上封装了了轻量级的数据库 Room,访问数据库更稳更快.
   Room持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同事,获享更强健的数据库访问机制,该库可以帮助您运行应用的设备上创建应用数据的缓存.此缓存应充当应用的单一可信来源,使用户能够在应用中查看关键的信息的一致副本,无论用户是否具有互联网链接.

原理:

image.png
  • 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())
            }
        }
        
    }
    
}

看运行截图


image.png

好了,到此为止,简单的使用已经完成了~~~~

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.


image.png

END

你可能感兴趣的:(Jetpack 之 Room 小白入手)