赞
踩
在 Android 中使用 Room 框架 , 创建 SQLite 数据库时 , 有时需要预填充一些数据 , 这些数据一般都是来自 assets 资源目录 ;
如果用户首次打开应用 , 就会从 assets 资源目录中获取 SQLite 数据库文件 , 将该文件中的数据读取出来 , 并存储到 Room 数据库中 ;
想要预填充数据 , 需要创建 SQLite 数据库文件 , 这里使用 DB Browser for SQLite 创建并查看 SQLite 数据库文件 ;
首先 , 下载 DB Browser for SQLite 数据库工具 , 下载地址是 , 官方地址已经挂了 , 这里是 CSDN 下载地址 https://download.csdn.net/download/han1202012/87904496 , 0 积分即可下载 ;
然后 , 安装 DB Browser for SQLite 数据库 ; 下载后的文件是 DB.Browser.for.SQLite-3.12.2-win64.msi 文件 ;
双击上述安装文件 , 运行安装程序 ,
同意许可协议 ,
创建快捷方式 ,
设置安装地址 , 默认在 C 盘 ,
这里 点击 Browse 按钮 , 改成 D 盘 ,
开始安装
等待安装完成 ,
DB Browser for SQLite 数据库工具 安装完毕 ;
打开 DB Browser for SQLite 数据库工具 , 界面如下图所示 ;
参考 【Jetpack】Room 中的销毁重建策略 ( 创建临时数据库表 | 拷贝数据库表数据 | 删除旧表 | 临时数据库表重命名 ) 博客 中的 版本 1 数据库表结构对应的 Entity 实体类代码 ,
@Entity(tableName = "student") class Student { /** * @PrimaryKey 设置主键 autoGenerate 为自增 * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型 */ @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER) var id: Int = 0 /** * 姓名字段 * 数据库表中的列名为 name * 数据库表中的类型为 TEXT 文本类型 */ @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT) lateinit var name: String /** * 年龄字段 * 数据库表中的列名为 age * 数据库表中的类型为 INTEGER 文本类型 */ @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER) var age: Int = 0 }
Room 实体类代码 , 在 DB Browser for SQLite 工具中 , 创建 student 表字段 ;
打开 DB Browser for SQLite 工具 , 选择 " 文件 / 新建数据库 " ,
设置数据库存储目录 , 并设置数据库名称 " init.db " ;
点击 " 保存 " 按钮后 , 会弹出为 刚创建的数据库 编辑表定义 对话框 ;
点击 " 增加 " 按钮 , 插入了一个默认 Field1 字段 , 类型是 INTEGER ,
将创建的第一个字段 , 名称设置为 id , 类型仍为 INTEGER 不变 , 将该字段设置为 非空 / 自增 / 主键 ;
生成的 SQL 语句如下 :
CREATE TABLE "" (
"id" INTEGER NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
);
继续添加 name 和 age 两个字段 ; 生成的 SQL 语句如下 :
CREATE TABLE "" (
"id" INTEGER NOT NULL,
"name" TEXT,
"age" INTEGER,
PRIMARY KEY("id" AUTOINCREMENT)
);
为数据库表设置名称 student ; 生成的 sql 语句如下所示 :
CREATE TABLE "student" (
"id" INTEGER NOT NULL,
"name" TEXT,
"age" INTEGER,
PRIMARY KEY("id" AUTOINCREMENT)
);
点击 " 编辑表定义 " 对话框中的 OK 按钮 , 即可创建数据库表成功 ; 创建后的数据库表如下 :
创建好数据库表之后 , 在 执行 SQL 面板界面 , 插入两条数据 ;
点击 三角形 的 执行按钮 , 即可执行下面的 SQL 语句 , 向 数据库 student 表中插入两条数据 ;
INSERT INTO student (name, age) VALUES ('Tom', 18);
INSERT INTO student (name, age) VALUES ('Jerry', 16);
在 浏览数据 面板中, 查看刚才插入的数据 ;
设置完毕后 , 保存数据 ;
最终 , 得到一个 db 类型的数据库文件 ;
将上个章节生成的 init.db 数据库文件拷贝到 assets 目录下 ,
然后在 RoomDatabase.Builder 构建器创建时 , 调用 RoomDatabase.Builder 构建器的 createFromAsset 函数 , 就可以自动从 assets 目录下自动读取 db 数据库文件中的数据 , 并将数据初始化本应用的数据库表中 ;
/** * 配置Room以使用位于的预打包数据库创建和打开数据库 * 应用程序“assets/”文件夹。 * * Room不打开预打包的数据库,而是将其复制到内部 * App数据库文件夹,然后打开它。预打包的数据库文件必须位于 * 应用程序的“assets/”文件夹。例如,位于的文件的路径 * “assets/databases/products.db”将变成“databases/products.db”。 * * 将验证预打包的数据库模式。最好是创建你的 * 预打包数据库模式时利用导出的模式文件生成 * (数据库。exportSchema]已启用。 * * 此方法不支持内存数据库[Builder]。 * * @param databaseFilePath 数据库文件所在的“assets/”目录中的文件路径。 * * @return This [Builder] instance. */ fun createFromAsset(databaseFilePath: String): RoomDatabase.Builder<T?> { mCopyFromAssetPath = databaseFilePath return this }
如果不设置 数据库 初始化数据 , 则输出的日志如下 :
2023-06-14 13:16:39.615 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: []
2023-06-14 13:16:40.019 I/Room_MainActivity: 插入数据 S1 : Student(id=0, name='Tom', age=18)
2023-06-14 13:16:40.024 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18)]
2023-06-14 13:16:40.522 I/Room_MainActivity: 插入数据 S2 : Student(id=0, name='Jerry', age=16)
2023-06-14 13:16:40.526 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jerry', age=16)]
2023-06-14 13:16:41.024 I/Room_MainActivity: 更新数据 S2 : Student(id=2, name='Jack', age=60)
2023-06-14 13:16:41.031 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jack', age=60)]
2023-06-14 13:16:41.530 I/Room_MainActivity: 删除数据 id = 1
2023-06-14 13:16:41.538 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=2, name='Jack', age=60)]
2023-06-14 13:16:42.032 I/Room_MainActivity: 主动查询 : LiveData : androidx.room.RoomTrackingLiveData@8896405 , 实际数据 : null
2023-06-14 13:16:42.037 I/Room_MainActivity: 主动查询2 : [Student(id=2, name='Jack', age=60)]
设置了 预填充数据 后 , 执行效果如下 :
2023-06-14 14:15:08.268 I/Room_MainActivity: 插入数据 S1 : Student(id=0, name='Tom', age=18)
2023-06-14 14:15:08.797 I/Room_MainActivity: 插入数据 S2 : Student(id=0, name='Jerry', age=16)
2023-06-14 14:15:09.329 I/Room_MainActivity: 更新数据 S2 : Student(id=2, name='Jack', age=60)
2023-06-14 14:15:09.865 I/Room_MainActivity: 删除数据 id = 1
2023-06-14 14:15:10.413 I/Room_MainActivity: 主动查询 : LiveData : androidx.room.RoomTrackingLiveData@9a0df02 , 实际数据 : null
2023-06-14 14:15:10.429 I/Room_MainActivity: 主动查询2 : [Student(id=6, name='Tom', age=18), Student(id=7, name='Jerry', age=16), Student(id=8, name='Tom', age=18), Student(id=9, name='Jerry', age=16)]
期间遇到该错误 , 报错信息如下 ;
2023-06-14 13:21:12.068 E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_0 Process: kim.hsl.rvl, PID: 18915 java.lang.RuntimeException: Exception while computing database live data. at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:764) Caused by: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: student(kim.hsl.rvl.Student). Expected: TableInfo{name='student', columns={name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, age=Column{name='age', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]} Found: TableInfo{name='student', columns={name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, age=Column{name='age', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]} at androidx.room.RoomOpenHelper.onCreate(RoomOpenHelper.java:82) at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onCreate(FrameworkSQLiteOpenHelper.java:118) at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:393) at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:298) at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:92) at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:53) at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:90) at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476) at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281) at androidx.room.RoomDatabase.query(RoomDatabase.java:324) at androidx.room.util.DBUtil.query(DBUtil.java:83) at kim.hsl.rvl.StudentDao_Impl$4.call(StudentDao_Impl.java:123) at kim.hsl.rvl.StudentDao_Impl$4.call(StudentDao_Impl.java:120) at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90) ... 3 more
分析下面的错误 :
期待获取的数据库表信息 :
TableInfo{name='student',
columns={
name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'},
age=Column{name='age', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'},
id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
实际获取的数据库表信息 :
TableInfo{name='student',
columns={
name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'},
id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'},
age=Column{name='age', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}
}, foreignKeys=[], indices=[]}
唯一的区别就是 age 字段的 非空属性不同 , 这里 在 DB Browser for SQLite 工具中设置 age 字段为非空字段 ;
右键点击数据库表 , 在弹出的右键菜单中 , 选择 " 修改表 " 选项 ,
将 age 属性设置为非空 ;
本博客中的代码是在上一篇博客 【Jetpack】Room 中的销毁重建策略 ( 创建临时数据库表 | 拷贝数据库表数据 | 删除旧表 | 临时数据库表重命名 ) 的基础上 , 添加了 由 DB Browser for SQLite 工具制作的 预填充数据 文件 ;
该实体类中 , 暂时只保留 id , name , age 三个字段 ;
package kim.hsl.rvl import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey /** * 定义数据库表 Entity 实体 / 同时定义数据库表 和 对鹰的实体类 * 设置该数据类对应数据库中的一张数据表, 表名为 student * 该数据库表中的数据对应一个 Student 类实例对象 */ @Entity(tableName = "student") class Student { /** * @PrimaryKey 设置主键 autoGenerate 为自增 * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型 */ @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER) var id: Int = 0 /** * 姓名字段 * 数据库表中的列名为 name * 数据库表中的类型为 TEXT 文本类型 */ @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT) lateinit var name: String /** * 年龄字段 * 数据库表中的列名为 age * 数据库表中的类型为 INTEGER 文本类型 */ @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER) var age: Int = 0 /** * 性别字段 * 数据库表中的列名为 sex * 数据库表中的类型为 TEXT 文本类型 */ /*@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.TEXT) var sex: String = "M"*/ /** * 性别字段 * 数据库表中的列名为 sex * 数据库表中的类型为 INTEGER 文本类型 */ /*@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER) var sex: Int = 0*/ /** * degree字段 * 数据库表中的列名为 sex * 数据库表中的类型为 INTEGER 文本类型 */ /*@ColumnInfo(name = "degree", typeAffinity = ColumnInfo.INTEGER) var degree: Int = 0*/ /** * 有些属性用于做业务逻辑 * 不需要插入到数据库中 * 使用 @Ignore 注解修饰该属性字段 */ @Ignore lateinit var studentInfo: String /** * 默认的构造方法给 Room 框架使用 */ constructor(id: Int, name: String, age: Int) { this.id = id this.name = name this.age = age } /** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */ @Ignore constructor(name: String, age: Int) { this.name = name this.age = age } /** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */ @Ignore constructor(id: Int) { this.id = id } override fun toString(): String { return "Student(id=$id, name='$name', age=$age)" } }
与之完全对应的数据库表如下 :
对应的 SQLite 数据库表创建语句如下 :
CREATE TABLE "student" (
"id" INTEGER NOT NULL,
"name" TEXT,
"age" INTEGER NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
);
在 RoomDatabase.Builder 构建器创建时 , 调用 RoomDatabase.Builder 构建器的 createFromAsset 函数 , 就可以自动从 assets 目录下自动读取 db 数据库文件中的数据 , 并将数据初始化本应用的数据库表中 ;
package kim.hsl.rvl 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], version = 1, exportSchema = true) abstract class StudentDatabase: RoomDatabase() { /** * 获取 数据库访问 对象 * 这是必须要实现的函数 */ abstract fun studentDao(): StudentDao companion object { lateinit var instance: StudentDatabase /** * 数据库版本 1 升级到 版本 2 的迁移类实例对象 */ val MIGRATION_1_2: Migration = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { Log.i("Room_StudentDatabase", "数据库版本 1 升级到 版本 2") database.execSQL("alter table student add column sex integer not null default 1") } } /** * 数据库版本 2 升级到 版本 3 的迁移类实例对象 */ val MIGRATION_2_3: Migration = object : Migration(2, 3) { override fun migrate(database: SupportSQLiteDatabase) { Log.i("Room_StudentDatabase", "数据库版本 2 升级到 版本 3") database.execSQL("alter table student add column degree integer not null default 1") } } /** * 数据库版本 3 升级到 版本 4 的迁移类实例对象 * 销毁重建策略 */ val MIGRATION_3_4: Migration = object : Migration(3, 4) { override fun migrate(database: SupportSQLiteDatabase) { Log.i("Room_StudentDatabase", "数据库版本 3 升级到 版本 4") // 创新临时数据库 database.execSQL( "CREATE TABLE temp_student (" + "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + "name TEXT," + "age INTEGER NOT NULL," + "sex TEXT NOT NULL DEFAULT 'M'," + "degree INTEGER NOT NULL DEFAULT 1)" ) // 拷贝数据 database.execSQL( "INSERT INTO temp_student (name, age, degree)" + "SELECT name, age, degree FROM student" ) // 删除原始表 database.execSQL("DROP TABLE student") // 将临时表命令为原表表明 database.execSQL("ALTER TABLE temp_student RENAME TO student") } } fun inst(context: Context): StudentDatabase { if (!::instance.isInitialized) { synchronized(StudentDatabase::class) { // 创建数据库 instance = Room.databaseBuilder( context.applicationContext, StudentDatabase::class.java, "student_database.db") .createFromAsset("init.db") /*.addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_3_4)*/ .fallbackToDestructiveMigration() .allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库 // 如果要在主线程操作数据库需要调用该函数 .build() } } return instance; } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。