数据库的每个表,都对应一个 Entity,一个 Dao(Dao 负责增删改查操作)
新建项目 Jetpack5RoomTest,在 build.gradle 添加如下依赖:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
dependencies {
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}
首先,新建 database/Student.kt,代码如下:
package com.bignerdranch.android.jetpack5roomtest.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(tableName = "student")
class Student {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
var id = 0
@ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
var name: String
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.TEXT)
var age: String
/**
* Room会使用这个构造器来存储数据,也就是当你从表中得到Student对象时候,Room会使用这个构造器
*/
constructor(id: Int, name: String, age: String) {
this.id = id
this.name = name
this.age = age
}
/**
* 由于Room只能识别和使用一个构造器,如果希望定义多个构造器,你可以使用Ignore标签,让Room忽略这个构造器
* 同样,@Ignore标签还可用于字段,使用@Ignore标签标记过的字段,Room不会持久化该字段的数据
*/
@Ignore
constructor(name: String, age: String) {
this.name = name
this.age = age
}
}
然后,新建 database/StudentDao.kt,封装增删改查操作,代码如下:
package com.bignerdranch.android.jetpack5roomtest.database
import androidx.room.*
@Dao
interface StudentDao {
@Insert
fun insertStudent(student: Student?)
@Delete
fun deleteStudent(student: Student?)
@Update
fun updateStudent(student: Student?)
@get:Query("SELECT * FROM student")
val studentList: List<Student?>?
@Query("SELECT * FROM student WHERE id = :id")
fun getStudentById(id: Int): Student?
}
然后,新建 database/MyDatabase.kt,其中有 studentDao 属性,代码如下:
package com.bignerdranch.android.jetpack5roomtest.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [Student::class], version = 1)
abstract class MyDatabase : RoomDatabase() {
abstract fun studentDao(): StudentDao?
companion object {
private const val DATABASE_NAME = "my_db"
private var databaseInstance: MyDatabase? = null
@Synchronized
fun getInstance(context: Context): MyDatabase? {
if (databaseInstance == null) {
databaseInstance = Room.databaseBuilder(context.applicationContext, MyDatabase::class.java, DATABASE_NAME).build()
}
return databaseInstance
}
}
}
activity_main.xml 的布局如下:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btnInsertStudent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:layout_gravity="center_horizontal"
android:textAllCaps="false"
android:text="Add a Student"/>
<ListView
android:id="@+id/lvStudent"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
LinearLayout>
activity_main.xml 的布局如下:
然后,新建 list_item_student.xml 布局,布局如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/tvId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"/>
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"/>
<TextView
android:id="@+id/tvAge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"/>
LinearLayout>
list_item_student.xml 的布局如下:
新建 dialog_layout_student.xml,布局如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Name"
android:layout_weight="1"
android:autofillHints="" />
<EditText
android:id="@+id/etAge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Age"
android:layout_weight="1"
android:autofillHints="" />
LinearLayout>
dialog_layout_student.xml 的效果如下:
接下来,在 MainActivity 中可通过 database 来操作数据库,代码如下:
package com.bignerdranch.android.jetpack5roomtest
import android.content.DialogInterface
import android.os.AsyncTask
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.AdapterView.OnItemLongClickListener
import android.widget.EditText
import android.widget.ListView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.bignerdranch.android.jetpack5roomtest.database.MyDatabase
import com.bignerdranch.android.jetpack5roomtest.database.Student
class MainActivity : AppCompatActivity() {
private var myDatabase: MyDatabase? = null
private var studentList: MutableList<Student?>? = ArrayList()
private var studentAdapter: StudentAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btnInsertStudent).setOnClickListener { openAddStudentDialog() }
val lvStudent: ListView = findViewById(R.id.lvStudent)
studentAdapter = StudentAdapter(this@MainActivity, studentList)
lvStudent.adapter = studentAdapter
lvStudent.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ ->
updateOrDeleteDialog((studentList as ArrayList<Student?>)[position])
false
}
myDatabase = MyDatabase.getInstance(this)
QueryStudentTask().execute()
}
private fun updateOrDeleteDialog(student: Student?) {
val options = arrayOf("更新", "删除")
AlertDialog.Builder(this@MainActivity)
.setTitle("")
.setItems(options, DialogInterface.OnClickListener { _, which ->
if (which == 0) {
openUpdateStudentDialog(student)
} else if (which == 1) {
if (student != null) {
DeleteStudentTask(student).execute()
}
}
}).show()
}
private fun openAddStudentDialog() {
val customView: View = this.layoutInflater.inflate(R.layout.dialog_layout_student, null)
val etName: EditText = customView.findViewById(R.id.etName)
val etAge: EditText = customView.findViewById(R.id.etAge)
val dialog: AlertDialog = AlertDialog.Builder(this@MainActivity).create()
dialog.setTitle("Add Student")
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", DialogInterface.OnClickListener { _, _ ->
if (TextUtils.isEmpty(etName.text.toString()) || TextUtils.isEmpty(etAge.text.toString())) {
Toast.makeText(this@MainActivity, "输入不能为空", Toast.LENGTH_SHORT).show()
} else {
InsertStudentTask(etName.text.toString(), etAge.text.toString()).execute()
}
})
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "CANCEL", DialogInterface.OnClickListener { _, _ -> dialog.dismiss() })
dialog.setView(customView)
dialog.show()
}
private fun openUpdateStudentDialog(student: Student?) {
if (student == null) {
return
}
val customView: View = this.layoutInflater.inflate(R.layout.dialog_layout_student, null)
val etName: EditText = customView.findViewById(R.id.etName)
val etAge: EditText = customView.findViewById(R.id.etAge)
etName.setText(student.name)
etAge.setText(student.age)
val dialog: AlertDialog = AlertDialog.Builder(this@MainActivity).create()
dialog.setTitle("Update Student")
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", DialogInterface.OnClickListener { _, _ ->
if (TextUtils.isEmpty(etName.text.toString()) || TextUtils.isEmpty(etAge.text.toString())) {
Toast.makeText(this@MainActivity, "输入不能为空", Toast.LENGTH_SHORT).show()
} else {
UpdateStudentTask(student.id, etName.text.toString(), etAge.text.toString()).execute()
}
})
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "CANCEL", DialogInterface.OnClickListener { dialog, _ -> dialog.dismiss() })
dialog.setView(customView)
dialog.show()
}
private inner class InsertStudentTask(var name: String, var age: String) : AsyncTask<Void?, Void?, Void?>() {
override fun doInBackground(vararg params: Void?): Void? {
myDatabase!!.studentDao()!!.insertStudent(Student(name, age))
studentList!!.clear()
studentList!!.addAll(myDatabase!!.studentDao()!!.studentList!!)
return null
}
override fun onPostExecute(result: Void?) {
super.onPostExecute(result)
studentAdapter?.notifyDataSetChanged()
}
}
private inner class UpdateStudentTask(var id: Int, var name: String, var age: String) : AsyncTask<Void?, Void?, Void?>() {
override fun onPostExecute(result: Void?) {
super.onPostExecute(result)
studentAdapter?.notifyDataSetChanged()
}
override fun doInBackground(vararg params: Void?): Void? {
myDatabase!!.studentDao()!!.updateStudent(Student(id, name, age))
studentList!!.clear()
studentList!!.addAll(myDatabase!!.studentDao()!!.studentList!!)
return null
}
}
private inner class DeleteStudentTask(var student: Student) : AsyncTask<Void?, Void?, Void?>() {
override fun onPostExecute(result: Void?) {
super.onPostExecute(result)
studentAdapter?.notifyDataSetChanged()
}
override fun doInBackground(vararg params: Void?): Void? {
myDatabase!!.studentDao()!!.deleteStudent(student)
studentList!!.clear()
studentList!!.addAll(myDatabase!!.studentDao()!!.studentList!!)
return null
}
}
private inner class QueryStudentTask : AsyncTask<Void?, Void?, Void?>() {
override fun onPostExecute(result: Void?) {
super.onPostExecute(result)
studentAdapter?.notifyDataSetChanged()
}
override fun doInBackground(vararg params: Void?): Void? {
studentList!!.clear()
studentList!!.addAll(myDatabase!!.studentDao()!!.studentList!!)
return null
}
}
}
新建 StudentAdapter.kt,绑定 activity_main.xml 的 ListView,代码如下:
package com.bignerdranch.android.jetpack5roomtest
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import com.bignerdranch.android.jetpack5roomtest.database.Student
class StudentAdapter(context: Context, private val data: MutableList<Student?>?) : BaseAdapter() {
private val layoutInflater: LayoutInflater
internal inner class ViewHolder {
var tvId: TextView? = null
var tvName: TextView? = null
var tvAge: TextView? = null
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
var convertView: View? = convertView
val viewHolder: ViewHolder
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.list_item_student, null)
viewHolder = ViewHolder()
viewHolder.tvId = convertView.findViewById(R.id.tvId)
viewHolder.tvName = convertView.findViewById(R.id.tvName)
viewHolder.tvAge = convertView.findViewById(R.id.tvAge)
convertView.tag = viewHolder
} else {
viewHolder = convertView.tag as ViewHolder
}
viewHolder.tvId!!.text = (data!![position]?.id).toString()
viewHolder.tvName!!.text = data[position]!!.name
viewHolder.tvAge!!.text = data[position]!!.age
return convertView
}
override fun getCount(): Int {
return data?.size ?: 0
}
override fun getItem(position: Int): Student? {
return data?.get(position)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
init {
layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
}
}
运行后,可以通过 Room 对数据库做添加、更新、删除、查询,效果如下:
项目github代码详见
当 Room 变化时,通过 ViewModel 内的 LiveData 通知页面数据的变化,架构如下:
有两种实例化数据库对象的方式:
新建项目(拷贝上文5.1节的项目),项目github详见。
首先,修改 StudentDao.kt,用 LiveData 将 List 包装起来,代码如下:
package com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest.database
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
interface StudentDao {
@Insert
fun insertStudent(student: Student?)
@Delete
fun deleteStudent(student: Student?)
@Update
fun updateStudent(student: Student?)
@Query("SELECT * FROM student")
fun getStudentList(): LiveData<List<Student?>?>? //希望监听学生表的变化,为其加上LiveData
@Query("SELECT * FROM student WHERE id = :id")
fun getStudentById(id: Int): Student?
}
其次,新建 StudentViewModel 类,该类继承自 AndroidViewModel,其中有 Database 和 LiveData,代码如下:
package com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest.database.MyDatabase
import com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest.database.Student
class StudentViewModel(application: Application) : AndroidViewModel(application) {
private val myDatabase: MyDatabase?
val liveDataStudent: LiveData<List<Student?>?>?
init {
myDatabase = MyDatabase.getInstance(application)
liveDataStudent = myDatabase!!.studentDao()!!.getStudentList()
}
}
然后,在 MainActivity 中初始化 List,实例化 StudentViewModel 并监听其 LiveData 的变化,代码如下:
package com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest
import android.content.DialogInterface
import android.os.AsyncTask
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.AdapterView.OnItemLongClickListener
import android.widget.EditText
import android.widget.ListView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest.database.MyDatabase
import com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest.database.Student
class MainActivity : AppCompatActivity() {
private var myDatabase: MyDatabase? = null
private var studentList: MutableList<Student?>? = null
private var studentAdapter: StudentAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btnInsertStudent).setOnClickListener { openAddStudentDialog() }
val lvStudent = findViewById<ListView>(R.id.lvStudent)
studentList = ArrayList()
studentAdapter = StudentAdapter(this@MainActivity, studentList)
lvStudent.adapter = studentAdapter
lvStudent.onItemLongClickListener = OnItemLongClickListener { parent, view, position, id ->
updateOrDeleteDialog((studentList as ArrayList<Student?>)[position])
false
}
myDatabase = MyDatabase.getInstance(this)
val studentViewModel: StudentViewModel = ViewModelProvider(this)[StudentViewModel::class.java]
studentViewModel.liveDataStudent!!.observe(this) { students ->
(studentList as ArrayList<Student?>).clear()
(studentList as ArrayList<Student?>).addAll(students!!)
studentAdapter!!.notifyDataSetChanged()
}
}
private fun updateOrDeleteDialog(student: Student?) {
val options = arrayOf("更新", "删除")
AlertDialog.Builder(this@MainActivity)
.setTitle("")
.setItems(options) { _, which ->
if (which == 0) {
openUpdateStudentDialog(student)
} else if (which == 1) {
if (student != null) {
DeleteStudentTask(student).execute()
}
}
}.show()
}
private fun openAddStudentDialog() {
val customView: View = this.layoutInflater.inflate(R.layout.dialog_layout_student, null)
val etName = customView.findViewById<EditText>(R.id.etName)
val etAge = customView.findViewById<EditText>(R.id.etAge)
val builder = AlertDialog.Builder(this@MainActivity)
val dialog = builder.create()
dialog.setTitle("Add Student")
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK") { dialog, which ->
if (TextUtils.isEmpty(etName.text.toString()) || TextUtils.isEmpty(etAge.text.toString())) {
Toast.makeText(this@MainActivity, "输入不能为空", Toast.LENGTH_SHORT).show()
} else {
InsertStudentTask(etName.text.toString(), etAge.text.toString()).execute()
}
}
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "CANCEL") { dialog, which -> dialog.dismiss() }
dialog.setView(customView)
dialog.show()
}
private fun openUpdateStudentDialog(student: Student?) {
if (student == null) {
return
}
val customView: View = this.layoutInflater.inflate(R.layout.dialog_layout_student, null)
val etName = customView.findViewById<EditText>(R.id.etName)
val etAge = customView.findViewById<EditText>(R.id.etAge)
etName.setText(student.name)
etAge.setText(student.age)
val builder = AlertDialog.Builder(this@MainActivity)
val dialog = builder.create()
dialog.setTitle("Update Student")
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK") { dialog, which ->
if (TextUtils.isEmpty(etName.text.toString()) || TextUtils.isEmpty(etAge.text.toString())) {
Toast.makeText(this@MainActivity, "输入不能为空", Toast.LENGTH_SHORT).show()
} else {
UpdateStudentTask(student.id, etName.text.toString(), etAge.text.toString()).execute()
}
}
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "CANCEL") { dialog, which -> dialog.dismiss() }
dialog.setView(customView)
dialog.show()
}
private inner class InsertStudentTask(var name: String, var age: String) :
AsyncTask<Void?, Void?, Void?>() {
override fun doInBackground(vararg params: Void?): Void? {
myDatabase!!.studentDao()!!.insertStudent(Student(name, age))
return null
}
}
private inner class UpdateStudentTask(var id: Int, var name: String, var age: String) :
AsyncTask<Void?, Void?, Void?>() {
override fun doInBackground(vararg params: Void?): Void? {
myDatabase!!.studentDao()!!.updateStudent(Student(id, name, age))
return null
}
}
private inner class DeleteStudentTask(var student: Student) : AsyncTask<Void?, Void?, Void?>() {
override fun doInBackground(vararg params: Void?): Void? {
myDatabase!!.studentDao()!!.deleteStudent(student)
return null
}
}
}
运行后,当 LiveData 数据变化时,更新 UI 即可,而不需要每次增删改后都必须用 QueryStudentTask()
来手动查询一次数据库,简化了系统,效果如下:
在 MyDatabase.kt 中用 Migration 升级数据库,代码如下:
@Database(entities = [Student::class], exportSchema = false, version = 1)
abstract class MyDatabase() : RoomDatabase() {
abstract fun studentDao(): StudentDao?
companion object {
private val DATABASE_NAME = "my_db"
private var databaseInstance: MyDatabase? = null
@Synchronized
fun getInstance(context: Context): MyDatabase? {
if (databaseInstance == null) {
databaseInstance = Room
.databaseBuilder(context.applicationContext, MyDatabase::class.java, DATABASE_NAME)
.fallbackToDestructiveMigration()
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_1_3, MIGRATION_3_4)
.build()
}
return databaseInstance
}
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
//do something
Log.d("MyDatabase", "MIGRATION_1_2")
}
}
private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
//do something
Log.d("MyDatabase", "MIGRATION_2_3")
}
}
private val MIGRATION_1_3: Migration = object : Migration(1, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
//do something
Log.d("MyDatabase", "MIGRATION_1_3")
}
}
val MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
Log.d("MyDatabase", "MIGRATION_3_4")
database.execSQL(
"CREATE TABLE temp_Student (" +
"id INTEGER PRIMARY KEY NOT NULL," +
"name TEXT," +
"age TEXT)"
)
database.execSQL(
"INSERT INTO temp_Student (id, name, age) " +
"SELECT id, name, age FROM Student"
)
database.execSQL("DROP TABLE Student")
database.execSQL("ALTER TABLE temp_Student RENAME TO Student")
}
}
}
}
然后,通过 .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_1_3, MIGRATION_3_4)
执行升级。通过 @Database(entities = [Student::class], version = 1)
可标记数据库的目标 verison。用 fallbackToDestructiveMigration() 避免升级失败的崩溃。
对于跨 version 的情况,例如如果当前手机设备上 sqlite 的数据库 version 是 1,要安装的 App 的数据库 version 是3,Room 会先找 Migration(1,3),若无则执行 Migration(1,2) 和 Migration(2,3)
schema 文件记录了数据库的变化,方便我们了解历次数据库操作是否成功。
在 build.gradle(app) 中指定 Schema 文件导出的位置,设置如下:
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
}
并在 MyDatabase.kt 中设置 exportSchema = true( @Database(entities = [Student::class], exportSchema = true, version = 2)
)。
运行后,即可在 app/schemas 下看到导出的数据库json文件,效果如下:
在 sqlite 修改表结构,例如把 Student 表的 age 字段从 INTEGET 改为 TEXT,需要销毁旧表并重建新表,分为如下步骤:
MyDatabase.kt 的代码如下:
val MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
Log.d("MyDatabase", "MIGRATION_3_4")
database.execSQL(
"CREATE TABLE temp_Student (" +
"id INTEGER PRIMARY KEY NOT NULL," +
"name TEXT," +
"age TEXT)"
)
database.execSQL(
"INSERT INTO temp_Student (id, name, age) " +
"SELECT id, name, age FROM Student"
)
database.execSQL("DROP TABLE Student")
database.execSQL("ALTER TABLE temp_Student RENAME TO Student")
}
}
运行后,数据库字段类型变化生效,效果如下:
首先,创建一个 student 的数据库,示例如下:
bash > sqlite3 student.db
sqlite> create table student(id integer not null, name text not null, age text not null, primary key(id autoincrement));
sqlite> insert into student values (1,'zhao','1'),(2,'qian','2'),(3,'sun','3');
设置 Student.kt 都为 NotNull,与上文的 student.db 的数据类型匹配,代码如下:
@Entity(tableName = "student")
class Student {
@NotNull
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
var id = 0
@NotNull
@ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
var name: String
@NotNull
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.TEXT)
var age: String
/**
* Room会使用这个构造器来存储数据,也就是当你从表中得到Student对象时候,Room会使用这个构造器
*/
constructor(id: Int, name: String, age: String) {
this.id = id
this.name = name
this.age = age
}
/**
* 由于Room只能识别和使用一个构造器,如果希望定义多个构造器,你可以使用Ignore标签,让Room忽略这个构造器
* 同样,@Ignore标签还可用于字段,使用@Ignore标签标记过的字段,Room不会持久化该字段的数据
*/
@Ignore
constructor(name: String, age: String) {
this.name = name
this.age = age
}
}
其次,新建 assets folder 目录,示例如下:
然后,新建 app/assets/databases,并在其中放置 student.db 文件,其位置如下:
然后,在 MyDatabase.kt 中,用 createFromAsset() 方法从 assets/database/students.db 创建 Room 数据库,代码如下:
package com.bignerdranch.android.jetpack5roomwithlivedataandviewmodeltest.database
import android.content.Context
import android.util.Log
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
@Database(entities = [Student::class], exportSchema = false, version = 1)
abstract class MyDatabase() : RoomDatabase() {
abstract fun studentDao(): StudentDao?
companion object {
private val DATABASE_NAME = "my_db"
private var databaseInstance: MyDatabase? = null
@Synchronized
fun getInstance(context: Context): MyDatabase? {
if (databaseInstance == null) {
databaseInstance = Room
.databaseBuilder(context.applicationContext, MyDatabase::class.java, DATABASE_NAME)
.createFromAsset("databases/student.db")
.fallbackToDestructiveMigration()
// .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_1_3, MIGRATION_3_4)
.build()
}
return databaseInstance
}
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
//do something
Log.d("MyDatabase", "MIGRATION_1_2")
}
}
private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
//do something
Log.d("MyDatabase", "MIGRATION_2_3")
}
}
private val MIGRATION_1_3: Migration = object : Migration(1, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
//do something
Log.d("MyDatabase", "MIGRATION_1_3")
}
}
val MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
Log.d("MyDatabase", "MIGRATION_3_4")
database.execSQL(
"CREATE TABLE temp_Student (" +
"id INTEGER PRIMARY KEY NOT NULL," +
"name TEXT," +
"age TEXT)"
)
database.execSQL(
"INSERT INTO temp_Student (id, name, age) " +
"SELECT id, name, age FROM Student"
)
database.execSQL("DROP TABLE Student")
database.execSQL("ALTER TABLE temp_Student RENAME TO Student")
}
}
}
}
运行后,从指定的 student.db 文件创建了数据库,示例如下: