You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Room迁移异常:预期与实际表结构一致仍报错

Room数据库迁移异常排查:表结构一致却提示未正确处理

异常日志

java.lang.RuntimeException: Exception while computing database live data.
        at androidx.room.RoomTrackingLiveData.refreshRunnable$lambda-0(RoomTrackingLiveData.kt:74)
        at androidx.room.RoomTrackingLiveData.lambda$UkyPj-RMUoTXOMbUuy5NWSwmo0E(Unknown Source:0)
        at androidx.room.-$$Lambda$RoomTrackingLiveData$UkyPj-RMUoTXOMbUuy5NWSwmo0E.run(Unknown Source:2)
        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:923)
     Caused by: java.lang.IllegalStateException: Migration didn't properly handle: emergency(hero.db.model.Emergency).
     Expected:
    TableInfo{name='emergency', columns={emergency_record_id=Column{name='emergency_record_id', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, vehicle_plate=Column{name='vehicle_plate', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, device_id=Column{name='device_id', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, user_id=Column{name='user_id', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, sent_to_cloud=Column{name='sent_to_cloud', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, location=Column{name='location', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=1, defaultValue='null'}, emergency_description=Column{name='emergency_description', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, timestamp=Column{name='timestamp', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='emergency', columns={emergency_record_id=Column{name='emergency_record_id', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, vehicle_plate=Column{name='vehicle_plate', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, device_id=Column{name='device_id', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, user_id=Column{name='user_id', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, sent_to_cloud=Column{name='sent_to_cloud', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, location=Column{name='location', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=1, defaultValue='null'}, emergency_description=Column{name='emergency_description', type='TEXT', affinity='2', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}, timestamp=Column{name='timestamp', type='INTEGER', affinity='3', notNull=notNull, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}

相关代码

Emergency实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity(tableName = AppDatabase.TABLE_EMERGENCY)
public class Emergency {

    @ColumnInfo(name = "emergency_record_id")
    private Integer emergencyRecordId;

    @ColumnInfo(name = "vehicle_plate")
    private String vehiclePlate;

    @ColumnInfo(name = "device_id")
    private String deviceId;

    @ColumnInfo(name = "user_id")
    private Long userId;

    @ColumnInfo(name = "sent_to_cloud")
    @JsonIgnore
    private Boolean sentToCloud = false;

    @ColumnInfo(name = "location")
    private LocationRoute location;

    @PrimaryKey
    @ColumnInfo(name = "id")
    private Long id;

    @ColumnInfo(name = "emergency_description")
    private String emergencyDescription;

    @ColumnInfo(name = "timestamp")
    private Date timeStamp;
}

迁移SQL语句

"CREATE TABLE IF NOT EXISTS " + TABLE_EMERGENCY + " (\n " +
                            "emergency_record_id    INTEGER                               ,\n " +
                            "vehicle_plate          TEXT                                  ,\n " +
                            "device_id              TEXT                                  ,\n " +
                            "user_id                INTEGER                               ,\n " +
                            "sent_to_cloud          INTEGER                               ,\n " +
                            "location               TEXT                                  ,\n " +
                            "id                     INTEGER     PRIMARY KEY       NOT NULL,\n " +
                            "emergency_description  TEXT                                  ,\n " +
                            "timestamp              INTEGER                               \n " +
                            " );

问题

日志显示预期与实际的emergency表结构完全一致,但Room仍提示迁移未正确处理该表,请问原因及解决方法?


原因分析

  1. 非空约束不匹配:日志中Room预期所有字段都带有NOT NULL约束,但迁移SQL仅给id字段添加了该约束,其他字段均未设置。即使实体类使用包装类型(如Integer、Long),Room注解处理器可能因Lombok注解(如@Data)或版本问题,错误地将这些字段标记为非空,导致预期与实际表结构的约束不一致。
  2. 默认值缺失:实体类中sentToCloud字段有默认值false,但迁移SQL未给sent_to_cloud字段设置DEFAULT 0(Room将Boolean类型存储为INTEGER,false对应0),导致Room预期该字段存在默认值,而实际表结构未定义。
  3. 旧表结构残留:使用CREATE TABLE IF NOT EXISTS时,若旧版本数据库已存在emergency表,会直接保留旧结构而非创建新表,即使SQL语句正确,也会导致实际结构与预期不符。

解决方法

1. 修正迁移SQL的约束与默认值

给所有对应预期非空的字段添加NOT NULL,并给sent_to_cloud设置默认值:

"CREATE TABLE IF NOT EXISTS " + TABLE_EMERGENCY + " (\n " +
"emergency_record_id    INTEGER    NOT NULL                    ,\n " +
"vehicle_plate          TEXT       NOT NULL                    ,\n " +
"device_id              TEXT       NOT NULL                    ,\n " +
"user_id                INTEGER    NOT NULL                    ,\n " +
"sent_to_cloud          INTEGER    NOT NULL  DEFAULT 0         ,\n " +
"location               TEXT       NOT NULL                    ,\n " +
"id                     INTEGER    PRIMARY KEY  NOT NULL       ,\n " +
"emergency_description  TEXT       NOT NULL                    ,\n " +
"timestamp              INTEGER    NOT NULL                    \n " +
" );

2. 清理旧表结构(需谨慎)

若旧表数据无需保留,可先删除旧表再创建新表,避免旧结构残留:

"DROP TABLE IF EXISTS " + TABLE_EMERGENCY + "; " +
"CREATE TABLE " + TABLE_EMERGENCY + " (\n " +
-- 字段定义同上
" );

注意:若需保留旧数据,需先创建临时表迁移数据,再替换原表。

3. 完善类型转换器

确保自定义类型(LocationRoute)和Date字段有对应的TypeConverter,Room才能正确映射类型。例如Date的转换器:

public class Converters {
    @TypeConverter
    public static Date fromTimestamp(Long value) {
        return value == null ? null : new Date(value);
    }

    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}

并在Database类上添加@TypeConverters(Converters.class)注解。

4. 升级Room版本

若为Room版本bug导致的结构对比异常,升级至最新稳定版(如2.5.0及以上)可解决部分兼容性问题。


内容的提问来源于stack exchange,提问作者Matheus Wandscheer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 11:51:32