向现有房间数据库添加一列,同时使用房间版本 2.4.0-alpha01 中提供的当前自动迁移功能

Posted

技术标签:

【中文标题】向现有房间数据库添加一列,同时使用房间版本 2.4.0-alpha01 中提供的当前自动迁移功能【英文标题】:Add a column to existing room database whilst using the current auto-migration feature available in room version 2.4.0-alpha01 【发布时间】:2021-11-25 05:39:21 【问题描述】:

我有一个包含数据的房间数据库,现在我想向其中添加另一列。

Room 版本 2.4.0-alpha01 及更高版本应该使自动迁移更容易,所以我这样使用它:

...
version = 2,
autoMigrations = 
        @AutoMigration(from = 1, to = 2)
        , 
exportSchema = true

然后在我的模型类中,我添加了新的列名并生成了它的 setter 和 getter,就像其他的一样。

房间文档说,如果房间由于复杂的架构更改而无法执行迁移,则会引发编译时错误。然而,就我而言,我收到一个关于预期架构和新架构(我添加了一列的架构)之间的差异的运行时错误。

以下是错误:

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:919)
     Caused by: java.lang.IllegalStateException: Migration didn't properly handle: accounts(com.bisform.susu.models.Account).
     Expected:
    TableInfoname='accounts', columns=accountSavings=Columnname='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountName=Columnname='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateTime=Columnname='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfDeposits=Columnname='numberOfDeposits', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', accountNumber=Columnname='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfWithdrawals=Columnname='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', accountPayoutDate=Columnname='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', phoneOfNextOfKin=Columnname='phoneOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountImageURI=Columnname='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountID=Columnname='accountID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null', lastUpdatedDate=Columnname='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', phoneNumber=Columnname='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountPayoutTime=Columnname='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', lastUpdatedTime=Columnname='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', nameOfNextOfKin=Columnname='nameOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateDate=Columnname='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', isPaidOut=Columnname='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', foreignKeys=[], indices=[]
     Found:
E/AndroidRuntime: TableInfoname='accounts', columns=accountSavings=Columnname='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountName=Columnname='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateTime=Columnname='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfDeposits=Columnname='numberOfDeposits', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', accountNumber=Columnname='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfWithdrawals=Columnname='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', accountPayoutDate=Columnname='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountImageURI=Columnname='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountID=Columnname='accountID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null', lastUpdatedDate=Columnname='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', phoneNumber=Columnname='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountPayoutTime=Columnname='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', lastUpdatedTime=Columnname='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateDate=Columnname='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', isPaidOut=Columnname='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', foreignKeys=[], indices=[]
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
...

请问我应该怎么做才能处理这个错误?如何使用当前存在的自动迁移功能获得正确迁移的空间?

【问题讨论】:

【参考方案1】:

请问我应该怎么做才能处理这个错误?如何使用当前存在的自动迁移功能获得正确迁移的空间?

我相信只有在您以某种方式忽略自动迁移时,您才会得到这个。我相信,使用您展示的代码的唯一方法是包含手动迁移,因为这会覆盖自动迁移。

测试,如下所示。

测试

此测试显示了将 AutoMigration 从 V1 覆盖到 V2 的效果,以及未覆盖时迁移的工作方式。

测试基于根据您的预期 (v2) 和发现 (v1) 消息构建的 Accounts 类。

所以 Accounts 类是:-

@Entity(tableName = "accounts")
class Accounts 

    //String phoneOfNextOfKin; //<<<<< For  V2 else commented out
    //String nameOfNextOfKin; //<<<<< for V2 else commented out

    String accountSavings;
    String accountName;
    String accountCreateTime;
    Integer numberOfDeposits;
    String accountNumber;
    int numberOfWithdrawals;
    String accountPayoutDate;
    String accountImageURI;
    @PrimaryKey
    Long accountId;
    String lastUpdatedDate;
    String phoneNumber;
    String accountPayoutTime;
    String lastUpdatedTime;
    String accountCreateDate;
    boolean isPaidOut;

注意带有注释 // 即为 V2 添加了 2 列 phoneOfNextOfKin 和 nameOfNextOfKin

一个不会在版本之间改变的@Dao 类:-

@Dao
abstract class AccountsDao 
    @Insert
    abstract long insert(Accounts accounts);
    @Query("SELECT * FROM accounts")
    abstract List<Accounts> getAllFromAccounts();

@Database 类:-

@Database(
        entities = Accounts.class,
        version = TheDatabase.DBVERSION
        /* following line, if no schema saved, needs to be commented out */
        , autoMigrations = @AutoMigration(from = 1, to = 2) 
)
abstract class TheDatabase extends RoomDatabase 
    abstract AccountsDao getAccountsDao();

    public static final int DBVERSION = 1; //<<<<< change accordingly

    private static volatile TheDatabase instance = null;

    static TheDatabase getInstance(Context context) 
        if (instance == null) 
            instance = Room.databaseBuilder(
                    context,
                    TheDatabase.class,
                    "accounts.db"
            )
                    .allowMainThreadQueries() //run on main thread for brevity and convenience
                    .addMigrations(MIGRATION_1_2) //<<<<< if included then Migration didn't properly handle:
                    .build();
        
        return instance;
    

    /* Only needed for creating error shown in question */
    /* doesn't hurt if no addMigrations or when creating V1 */
    static final Migration MIGRATION_1_2 = new Migration(1,2) 
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) 

        
    ;

最后是以下活动:-

public class MainActivity extends AppCompatActivity 

    TheDatabase db;
    AccountsDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = TheDatabase.getInstance(this);
        dao = db.getAccountsDao();

        Accounts a = new Accounts();
        a.accountCreateDate = "2021-01-01";
        a.accountCreateTime = "08:00:00";
        a.accountImageURI = "imageURI";
        a.accountName = "MyAccount";
        a.accountNumber = "0000000000";
        a.accountPayoutDate = "2021-05-05";
        a.accountPayoutTime = "10:00:00";
        a.accountSavings = "savings";
        a.lastUpdatedDate = "2021-05-03";
        a.lastUpdatedTime = "09:15:00";
        a.numberOfDeposits = 10;
        a.numberOfWithdrawals = 5;
        a.isPaidOut = false;

        //a.nameOfNextOfKin = "Mr Next of Kin"; //<<<<< For V2
        //a.phoneOfNextOfKin = "1111111111"; //<<<<< For V2

        dao.insert(a);
        for(Accounts account: dao.getAllFromAccounts()) 
            String msg = "Account is " + account.accountName + " ID is " + account.accountId;
            if (TheDatabase.DBVERSION > 1) 
                //msg = msg + " next of kin is " + account.nameOfNextOfKin; //<<<<< For V2
            
            Log.d("ACCOUNTINFO", msg);
        
    

运行和结果:-

    使用上述代码(不包括 V2 代码)并且未安装应用程序。即创建 V1 数据库:-

App 运行成功,Database Inspector 显示:-

架构与单行一样符合预期。

    再次运行,无需任何更改
App 运行成功,Database Inspector 显示:-

3.对 V2 所做的更改值得注意:-

包括近亲的帐户名称和电话。

在 TheDatabase DBVERSION 中从 1 更改为 2 并且 .addMigrations left included

在调用活动中,名称和近亲值被赋值而不是被注释掉。 msg 变量被连接以包含近亲。

应用程序崩溃

:-

Caused by: java.lang.IllegalStateException: Migration didn't properly handle: accounts(a.a.so69442030javaroomautomigrationsaddcolumns.Accounts).
     Expected:
2021-10-05 14:31:50.669 30268-30268/a.a.so69442030javaroomautomigrationsaddcolumns E/AndroidRuntime: TableInfoname='accounts', columns=accountSavings=Columnname='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountName=Columnname='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateTime=Columnname='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfDeposits=Columnname='numberOfDeposits', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null', accountNumber=Columnname='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfWithdrawals=Columnname='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', phoneOfNextOfKin=Columnname='phoneOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountPayoutDate=Columnname='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountImageURI=Columnname='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountId=Columnname='accountId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null', lastUpdatedDate=Columnname='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', phoneNumber=Columnname='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountPayoutTime=Columnname='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', nameOfNextOfKin=Columnname='nameOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', lastUpdatedTime=Columnname='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateDate=Columnname='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', isPaidOut=Columnname='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', foreignKeys=[], indices=[]
     Found:
2021-10-05 14:31:50.670 30268-30268/a.a.so69442030javaroomautomigrationsaddcolumns E/AndroidRuntime: TableInfoname='accounts', columns=accountSavings=Columnname='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountName=Columnname='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateTime=Columnname='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfDeposits=Columnname='numberOfDeposits', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null', accountNumber=Columnname='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', numberOfWithdrawals=Columnname='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', accountPayoutDate=Columnname='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountImageURI=Columnname='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountId=Columnname='accountId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null', lastUpdatedDate=Columnname='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', phoneNumber=Columnname='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountPayoutTime=Columnname='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', lastUpdatedTime=Columnname='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', accountCreateDate=Columnname='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null', isPaidOut=Columnname='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', foreignKeys=[], indices=[]
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
即复制遇到的故障

    在卸载应用程序并使用 V1 中的代码后重新运行步骤 1-2(以从头开始演示整个过程)(重新运行注释掉或删除的 .addMigrations 将解决该问题)

    已应用第 3 步的更改但额外数据库构建中的 .addMigrations 行已被注释掉。

应用程序不会崩溃。数据库检查器在新插入的行中显示修改后的架构和适当的数据(另外 2 个添加的两个列为空):-

【讨论】:

非常感谢。但是卸载应用程序并重新安装它是否意味着数据库被完全破坏并重新创建?因此您会丢失所有现有数据?如果是这种情况,则会创建一个全新的数据库和架构,因此您不会得到预期的架构和发现架构的异常? @ZiyaadShiraz 是的,这是正确的。但是,我并不是说要卸载作为修复程序。我卸载以演示一个完整的周期(创建 v1,将更多数据添加到 V1 自动迁移(没有迁移设想的问题)到 V2)。 我本可以在删除 Migrate 的崩溃后进入 V2【参考方案2】:

解决上述问题所需要做的就是清理项目并重建。我想旧的架构不知何故仍在内存中,因此崩溃了。

【讨论】:

以上是关于向现有房间数据库添加一列,同时使用房间版本 2.4.0-alpha01 中提供的当前自动迁移功能的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程实战总结

为啥添加一列时所有行都得到空值?

使用 numpy.npv 函数的输出向数据框中添加一列

向熊猫数据框添加一列

从两个 Pandas DataFrames 向数据帧添加一列,当前使用两个带有条件的循环:有更快的方法吗?

netTiers 数据库模式向后兼容性