• 【Jetpack】Room 预填充数据 ( 安装 DB Browser for SQLite 工具 | 创建数据库文件 | 应用中设预填充数据对应的数据库文件 | 预填充数据库表字段属性必须一致 )






    一、Room 预填充数据简介



    在 Android 中使用 Room 框架 , 创建 SQLite 数据库时 , 有时需要预填充一些数据 , 这些数据一般都是来自 assets 资源目录 ;

    如果用户首次打开应用 , 就会从 assets 资源目录中获取 SQLite 数据库文件 , 将该文件中的数据读取出来 , 并存储到 Room 数据库中 ;





    二、安装 DB Browser for SQLite 数据库查看工具



    想要预填充数据 , 需要创建 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 数据库工具 , 界面如下图所示 ;
    在这里插入图片描述





    三、使用 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    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)
    );
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    继续添加 name 和 age 两个字段 ; 生成的 SQL 语句如下 :

    CREATE TABLE "" (
    	"id"	INTEGER NOT NULL,
    	"name"	TEXT,
    	"age"	INTEGER,
    	PRIMARY KEY("id" AUTOINCREMENT)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    为数据库表设置名称 student ; 生成的 sql 语句如下所示 :

    CREATE TABLE "student" (
    	"id"	INTEGER NOT NULL,
    	"name"	TEXT,
    	"age"	INTEGER,
    	PRIMARY KEY("id" AUTOINCREMENT)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    点击 " 编辑表定义 " 对话框中的 OK 按钮 , 即可创建数据库表成功 ; 创建后的数据库表如下 :

    在这里插入图片描述

    创建好数据库表之后 , 在 执行 SQL 面板界面 , 插入两条数据 ;

    点击 三角形 的 执行按钮 , 即可执行下面的 SQL 语句 , 向 数据库 student 表中插入两条数据 ;

    INSERT INTO student (name, age) VALUES ('Tom', 18);
    INSERT INTO student (name, age) VALUES ('Jerry', 16);
    
    • 1
    • 2

    在这里插入图片描述

    在 浏览数据 面板中, 查看刚才插入的数据 ;

    在这里插入图片描述

    设置完毕后 , 保存数据 ;

    最终 , 得到一个 db 类型的数据库文件 ;

    在这里插入图片描述





    四、应用中设预填充数据对应的数据库文件




    1、数据准备


    将上个章节生成的 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2、原执行结果


    如果不设置 数据库 初始化数据 , 则输出的日志如下 :

    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)]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述


    3、预填充数据后的执行结果


    设置了 预填充数据 后 , 执行效果如下 :

    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)]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述





    五、预填充数据报错信息 - 数据库字段属性必须完全相同



    期间遇到该错误 , 报错信息如下 ;

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述
    分析下面的错误 :

    期待获取的数据库表信息 :

    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=[]}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    实际获取的数据库表信息 :

    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=[]}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    唯一的区别就是 age 字段的 非空属性不同 , 这里 在 DB Browser for SQLite 工具中设置 age 字段为非空字段 ;

    右键点击数据库表 , 在弹出的右键菜单中 , 选择 " 修改表 " 选项 ,

    在这里插入图片描述

    将 age 属性设置为非空 ;

    在这里插入图片描述





    六、完整代码示例



    本博客中的代码是在上一篇博客 【Jetpack】Room 中的销毁重建策略 ( 创建临时数据库表 | 拷贝数据库表数据 | 删除旧表 | 临时数据库表重命名 ) 的基础上 , 添加了 由 DB Browser for SQLite 工具制作的 预填充数据 文件 ;



    1、Entity 实体类代码


    该实体类中 , 暂时只保留 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)"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    与之完全对应的数据库表如下 :
    在这里插入图片描述

    对应的 SQLite 数据库表创建语句如下 :

    CREATE TABLE "student" (
    	"id"	INTEGER NOT NULL,
    	"name"	TEXT,
    	"age"	INTEGER NOT NULL,
    	PRIMARY KEY("id" AUTOINCREMENT)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、RoomDatabase 类代码


    在 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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
  • 相关阅读:
    Webpack模块联邦:微前端架构的新选择
    1688商品详情接口代码展示
    Win10 蓝屏CRITICAL_PROCESS_DIED值为 0x000000EF
    线性表——链表(c语言)
    596. 超过5名学生的课
    springboot整合redis,并使用@Cacheable等注解进行缓存
    MMdetection3.x个人笔记
    【设计模式】观察者模式(行为型)⭐⭐⭐
    无法上网问题解决过程
    手动实现简单限流函数
  • 原文地址:https://blog.csdn.net/han1202012/article/details/131199743