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仍提示迁移未正确处理该表,请问原因及解决方法?
原因分析
- 非空约束不匹配:日志中Room预期所有字段都带有
NOT NULL约束,但迁移SQL仅给id字段添加了该约束,其他字段均未设置。即使实体类使用包装类型(如Integer、Long),Room注解处理器可能因Lombok注解(如@Data)或版本问题,错误地将这些字段标记为非空,导致预期与实际表结构的约束不一致。 - 默认值缺失:实体类中
sentToCloud字段有默认值false,但迁移SQL未给sent_to_cloud字段设置DEFAULT 0(Room将Boolean类型存储为INTEGER,false对应0),导致Room预期该字段存在默认值,而实际表结构未定义。 - 旧表结构残留:使用
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
相关产品推荐
相关产品推荐

