android room中实体类变化以后如何迁移

当你在 Room 数据库中修改实体(Entity)时,你需要正确地处理数据库迁移(Migration)。以下是处理方案:

基础说明

1. 理解迁移基础

Room 使用 版本号 来跟踪数据库结构(Schema)的变化。当你修改实体(例如:添加 / 删除列、更改数据类型)时,你必须:

  • 增加数据库版本号
  • 提供一个迁移策略,以将数据从旧结构转换到新结构。

2. 使用 Migration 类定义迁移

处理迁移的主要方式是创建一个 Migration 对象,该对象指定了如何将旧结构转换为新结构。这通常通过 SQL 语句 来完成。

步骤:
  1. 增加数据库版本号 在你的 @Database 注解中,增加 version 的数值(例如,从 1 增加到 2)。

    kotlin

    复制代码
    @Database(
        entities = [User::class],
        version = 2, // 从 1 增加到 2
        exportSchema = true // 推荐开启,用于跟踪结构变化
    )
    abstract class AppDatabase : RoomDatabase() {
        abstract fun userDao(): UserDao
    }
  2. 创建一个 Migration 对象 定义一个从旧版本(例如 1)到新版本(例如 2)的迁移。使用 SQL 来修改表。

    kotlin

    复制代码
    val MIGRATION_1_TO_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            // 示例:在 "User" 表中添加一个新列 "email"
            database.execSQL("ALTER TABLE User ADD COLUMN email TEXT")
        }
    }
  3. 将迁移添加到数据库构建器在构建数据库实例时,将迁移包含进去。

    kotlin

    复制代码
    val db = Room.databaseBuilder(
        context.applicationContext,
        AppDatabase::class.java,
        "app_database"
    )
    .addMigrations(MIGRATION_1_TO_2) // 添加迁移
    .build()

3. 处理复杂迁移

对于更复杂的更改(例如:重命名表 / 列、更改数据类型、拆分表),请使用 两步法

  1. 创建一个具有新结构的临时表。
  2. 将数据从旧表复制到临时表。
  3. 删除旧表,并将临时表重命名为原来的表名。
示例:重命名列

假设你想将 User 表中的 username 列重命名为 full_name

kotlin

复制代码
val MIGRATION_1_TO_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        // 1. 创建一个具有新结构的临时表
        database.execSQL("""
            CREATE TABLE IF NOT EXISTS User_new (
                id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                full_name TEXT,
                age INTEGER
            )
        """.trimIndent())

        // 2. 将数据从旧表复制到新表
        database.execSQL("""
            INSERT INTO User_new (id, full_name, age)
            SELECT id, username, age FROM User
        """.trimIndent())

        // 3. 删除旧表并将新表重命名
        database.execSQL("DROP TABLE User")
        database.execSQL("ALTER TABLE User_new RENAME TO User")
    }
}

4. 使用自动迁移(Auto-Migration)(Room 2.4.0+)

对于简单的结构变化(例如:添加一个带有默认值的可空列),如果你启用了 exportSchema 并使用了 @ColumnInfo(defaultValue = "..."),Room 可以 自动生成迁移代码

步骤:
  1. @Database 中启用 exportSchema(推荐)。

  2. 添加一个带有默认值的新列。

    kotlin

    复制代码
    @Entity
    data class User(
        @PrimaryKey val id: Int,
        val name: String,
        @ColumnInfo(defaultValue = "0") val age: Int = 0 // 带有默认值的新列
    )
  3. 增加数据库版本号。Room 会自动生成迁移代码。

    kotlin

    复制代码
    @Database(
        entities = [User::class],
        version = 2,
        exportSchema = true
    )
    abstract class AppDatabase : RoomDatabase() { ... }

    注意:自动迁移适用于以下情况:

    • 添加带有默认值的可空列。
    • 添加 @ColumnInfo(index = true)(创建索引)。
    • 更改 @ColumnInfo(defaultValue)(如果列是可空的)。

5. 测试迁移

务必测试迁移以确保数据完整性。使用 Room.inMemoryDatabaseBuilder 进行测试,并验证:

  • 数据是否被正确保留。
  • 结构变化是否按预期应用。
示例测试:

kotlin

复制代码
@Test
fun migrationFrom1To2_keepsData() {
    // 1. 创建一个版本为 1 的数据库
    val dbV1 = Room.inMemoryDatabaseBuilder(
        context,
        AppDatabase::class.java
    )
    .addMigrations() // 暂时不添加任何迁移
    .build()

    // 2. 向版本 1 的数据库中插入测试数据
    val userDaoV1 = dbV1.userDao()
    userDaoV1.insert(User(id = 1, name = "John"))

    // 3. 关闭数据库,并使用版本 2 和迁移重新打开它
    dbV1.close()
    val dbV2 = Room.inMemoryDatabaseBuilder(
        context,
        AppDatabase::class.java
    )
    .addMigrations(MIGRATION_1_TO_2)
    .build()

    // 4. 验证数据是否被保留
    val userV2 = dbV2.userDao().getUserById(1)
    assertThat(userV2?.email).isNull() // 新列应为 null (如果没有默认值)
    dbV2.close()
}

6. 处理边缘情况

  • 数据丢失:在删除列之前,务必先迁移数据。
  • 约束 :确保带有 NOT NULL 约束的新列有默认值,或者在迁移过程中被填充。
  • 回滚:Room 不支持自动回滚。请彻底测试迁移以防止数据损坏。

最佳实践

  1. 始终增加版本号:切勿重复使用或降低数据库版本号。

  2. 导出结构(Export Schema) :设置 exportSchema = true 以生成 JSON 文件来跟踪结构变化(存储在 schemas/ 目录下)。

  3. 测试迁移:为每一个迁移编写单元测试,以便及早发现问题。

  4. 使用事务 :将迁移逻辑包装在 database.beginTransaction()database.setTransactionSuccessful() 中,以确保原子性。

    kotlin

    复制代码
    val MIGRATION_1_TO_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.beginTransaction()
            try {
                // 迁移逻辑代码放在这里
                database.setTransactionSuccessful()
            } finally {
                database.endTransaction()
            }
        }
    }

总结

方案 适用场景 实现方式
Migration 简单 / 复杂的结构变化 定义 SQL 语句来转换数据
自动迁移 简单变化(添加可空列等) 启用 exportSchema 并使用默认值
两步法迁移 重命名表 / 列、更改数据类型 创建临时表、复制数据、重命名

通过遵循这些方案,你可以安全地修改 Room 数据库的结构,同时保留用户数据。

实际开发

1、报错截图

2、解决

加上.fallbacktoDestructiveMigration

升级一下版本号,然后再运行,就可以解决报错的问题。

相关推荐
Jomurphys1 小时前
设计模式 - 适配器模式 Adapter Pattern
android
雨白1 小时前
电子书阅读器:解析 EPUB 底层原理与实战
android·html
g***B7381 小时前
Kotlin协程在Android中的使用
android·开发语言·kotlin
A***27951 小时前
Kotlin反射机制
android·开发语言·kotlin
2501_916007471 小时前
iOS 应用性能测试的工程化流程,构建从指标采集到问题归因的多工具协同测试体系
android·ios·小程序·https·uni-app·iphone·webview
源码_V_saaskw1 小时前
JAVA国际版同城跑腿源码快递代取帮买帮送同城服务源码支持Android+IOS+H5
android·java·ios·微信小程序
q***d1732 小时前
Kotlin在后台服务中的框架
android·开发语言·kotlin
我要添砖java2 小时前
<JAVAEE> 多线程4-wait和notify方法
android·java·java-ee
Mr_万能胶2 小时前
到底原研药,来瞧瞧 Google 官方《Android API 设计指南》
android·架构·android studio