如何在Room数据库执行迁移并将主键改为id+doorway_id复合主键?
Room复合主键设置与迁移指南
没问题,Room完全支持复合主键的配置,下面我会一步步教你实现这个需求,包括实体类修改和数据库迁移的具体步骤。
一、修改实体类实现复合主键
要把id和doorway_id设为复合主键,你需要调整@Entity注解的配置,同时确保doorway_id字段存在于你的实体类中。具体操作如下:
- 移除原
_id字段上的@PrimaryKey注解 - 在
@Entity注解中通过primaryKeys参数指定复合主键的字段名数组 - 添加
doorway_id字段(如果你的实体类里还没有的话)
修改后的实体类代码示例:
@Entity(tableName = "hours", primaryKeys = {COLUMN_ID, COLUMN_DOORWAY_ID}) public class Hour extends EntityModel implements Parcelable { // 定义常量,方便维护 public static final String COLUMN_ID = "id"; public static final String COLUMN_DOORWAY_ID = "doorway_id"; public static final String COLUMN_SERVER_ID = "server_id"; public static final String COLUMN_ENTRIES = "entries"; public static final String COLUMN_ALERTS = "alerts"; public static final String COLUMN_MAG_LEVEL = "mag_level"; @SerializedName("id") @ColumnInfo(name = COLUMN_ID) @Expose() private int _id; // 新增doorway_id字段 @ColumnInfo(name = COLUMN_DOORWAY_ID) private int doorwayId; @SerializedName("server_id") @ColumnInfo(name = COLUMN_SERVER_ID) private int serverId; @SerializedName("entries") @ColumnInfo(name = COLUMN_ENTRIES) @Expose() private float entries; @SerializedName("alerts") @ColumnInfo(name = COLUMN_ALERTS) @Expose() private float alerts; @SerializedName("mag_level") @ColumnInfo(name = COLUMN_MAG_LEVEL) private float magLevel; // 这里省略Parcelable实现和其他构造、getter/setter方法 }
二、数据库迁移实现
因为修改主键属于破坏性结构变更,Room的自动迁移(AutoMigration)无法处理这类操作,所以必须编写手动迁移逻辑。具体步骤如下:
1. 升级数据库版本号
找到你的RoomDatabase子类,把版本号从原来的数值加1,比如原来的version = 1改成version = 2:
@Database(entities = {Hour.class}, version = 2) public abstract class AppDatabase extends RoomDatabase { // 你的DAO定义等内容 }
2. 编写手动迁移类
创建一个Migration实例,在migrate方法中执行SQL语句完成数据迁移。这里需要注意:由于主键变更,我们需要创建临时表来过渡数据,步骤是:
- 创建和新表结构一致的临时表
- 将原表的数据迁移到临时表(注意处理新增的
doorway_id字段,这里假设你要给它设置默认值0,你可以根据实际需求调整) - 删除原表
- 将临时表重命名为原表名
示例代码:
static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(@NonNull SupportSQLiteDatabase database) { // 1. 创建临时表,结构和新的hours表一致 database.execSQL("CREATE TABLE temp_hours (" + "id INTEGER NOT NULL, " + "doorway_id INTEGER NOT NULL, " + "server_id INTEGER, " + "entries REAL, " + "alerts REAL, " + "mag_level REAL, " + "PRIMARY KEY(id, doorway_id))"); // 2. 将原表数据迁移到临时表,这里给doorway_id设默认值0 database.execSQL("INSERT INTO temp_hours (id, doorway_id, server_id, entries, alerts, mag_level) " + "SELECT id, 0 AS doorway_id, server_id, entries, alerts, mag_level FROM hours"); // 3. 删除原表 database.execSQL("DROP TABLE hours"); // 4. 重命名临时表为原表名 database.execSQL("ALTER TABLE temp_hours RENAME TO hours"); } };
3. 将迁移类添加到数据库构建器中
在创建RoomDatabase实例时,添加这个迁移类:
AppDatabase db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "your-db-name") .addMigrations(MIGRATION_1_2) .build();
三、注意事项
- 数据填充:迁移时的
doorway_id默认值要根据你的业务逻辑调整,如果有其他来源可以获取这个值,要替换掉示例中的0 - 测试迁移:一定要在测试环境充分验证迁移逻辑,避免数据丢失
- 开发阶段快捷方式:如果是开发初期,数据不重要,可以使用
fallbackToDestructiveMigration(),它会直接删除旧表重建,但生产环境绝对不能用 - 字段类型一致性:确保临时表的字段类型和新表完全一致,避免迁移时出现类型不兼容问题
内容的提问来源于stack exchange,提问作者asim mehmood
相关产品推荐
相关产品推荐

