迁移概述
在Android应用中使用Room数据库时,当数据库结构需要变更(如新增字段、修改字段类型、删除表等)时,需要进行数据库迁移,以确保用户数据不会丢失。
迁移步骤
修改实体类:
在需要变更的实体类中添加、修改或删除字段。
例如,在User实体类中新增一个email字段:
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
public String name;
public int age;
public String email; // 新增字段
}
更新数据库版本号:
在RoomDatabase子类中,将version属性值增加1。
例如,将版本号从1升级到2:
@Database(entities = {User.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
// ...
}
定义Migration对象:
创建一个Migration对象,指定起始版本和目标版本,并在migrate方法中编写SQL语句来更新数据库结构。
例如,从版本1迁移到版本2,新增email字段:
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE users ADD COLUMN email TEXT");
}
};
在RoomDatabase子类中注册迁移:
在构建RoomDatabase实例时,通过addMigrations方法注册迁移对象。
例如:
Room.databaseBuilder(context, AppDatabase.class, "app_database")
.addMigrations(MIGRATION_1_2)
.build();
注意事项
备份数据:在进行数据库迁移前,务必备份用户数据,以防迁移过程中出现意外导致数据丢失。
测试迁移:在发布应用前,务必在测试环境中测试迁移过程,确保迁移能够成功执行且数据不会丢失。
处理复杂迁移:对于复杂的迁移(如修改字段类型、删除表等),可能需要创建临时表、使用事务等策略来确保数据的安全迁移。
1. 数据库迁移的实现步骤
(1) 定义 Migration 对象
创建一个 Migration 对象,指定 起始版本(startVersion) 和 目标版本(endVersion)。
在 migrate() 方法中编写 SQL 语句 来更新数据库结构。
(2) 在 RoomDatabase 子类中添加迁移
在构建 RoomDatabase 时,通过 addMigrations() 方法注册迁移逻辑。
2. 示例代码
(1) 定义 Migration
假设:
版本 1 的数据库只有 users 表(id, name, age)。
版本 2 需要新增 email 列。
java
// 定义迁移:从版本 1 升级到版本 2
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// 执行 SQL 语句,新增 email 列
database.execSQL("ALTER TABLE users ADD COLUMN email TEXT");
}
};
// 如果还有更复杂的迁移(如版本 2 → 3)
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// 例如:新增一个表
database.execSQL("CREATE TABLE IF NOT EXISTS orders (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"user_id INTEGER, " +
"product_name TEXT, " +
"FOREIGN KEY(user_id) REFERENCES users(id))");
}
};
(2) 在 RoomDatabase 中注册迁移
java
@Database(entities = {User.class}, version = 3) // 当前数据库版本
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class,
"app_database" // 数据库名称
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3) // 注册迁移
.build();
}
}
}
return INSTANCE;
}
}
3. 关键点说明
(1) 迁移的触发条件
当 version 变化 时(如从 1 升级到 2),Room 会检查是否提供了对应的 Migration。
如果没有提供迁移,Room 会 默认删除整个数据库并重建(导致数据丢失!)。
(2) 多版本迁移
如果用户直接从 版本 1 升级到版本 3,Room 会 自动应用所有必要的迁移(1→2 和 2→3)。
但如果中间版本缺失(如 1→3 缺少 1→2),会抛出 IllegalStateException。
(3) 破坏性变更(Fallback)
如果迁移太复杂(如删除表、重构数据),可以 强制销毁旧数据库:
java
Room.databaseBuilder(context, AppDatabase.class, "app_database")
.fallbackToDestructiveMigration() // 无法迁移时直接重建数据库(数据丢失!)
.build();
或者指定允许的版本范围:
java
Room.databaseBuilder(context, AppDatabase.class, "app_database")
.fallbackToDestructiveMigrationFrom(2) // 仅当从版本 2 升级时允许销毁重建
.build();
4. 完整流程示例
(1) 初始数据库(版本 1)
java
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
public String name;
public int age;
}
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase { ... }
(2) 升级到版本 2(新增 email 列)
java
// 1. 修改 User 实体类
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
public String name;
public int age;
public String email; // 新增字段
}
// 2. 定义迁移
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE users ADD COLUMN email TEXT");
}
};
// 3. 更新数据库版本并注册迁移
@Database(entities = {User.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase { ... }
(3) 升级到版本 3(新增 orders 表)
java
// 1. 定义 Order 实体类
@Entity(tableName = "orders", foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id", childColumns = "user_id"))
public class Order {
@PrimaryKey
public int id;
public int userId;
public String productName;
}
// 2. 修改数据库类
@Database(entities = {User.class, Order.class}, version = 3)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
public abstract OrderDao orderDao(); // 新增 DAO
}
// 3. 定义迁移
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS orders (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"user_id INTEGER, " +
"product_name TEXT, " +
"FOREIGN KEY(user_id) REFERENCES users(id))");
}
};
// 4. 注册迁移
Room.databaseBuilder(context, AppDatabase.class, "app_database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build();
5. 测试迁移
在测试时,可以 手动降级数据库版本 来验证迁移逻辑:
java
// 删除旧数据库(模拟用户从旧版本升级)
context.deleteDatabase("app_database");
// 创建版本 1 的数据库
RoomDatabase db = Room.databaseBuilder(context, AppDatabase.class, "app_database")
.createFromAsset("databases/version1.db") // 可选:从预置数据库文件初始化
.build();
// 升级到版本 2(触发迁移)
db.close();
AppDatabase upgradedDb = Room.databaseBuilder(context, AppDatabase.class, "app_database")
.addMigrations(MIGRATION_1_2)
.build();
6. 总结
关键步骤 代码示例
定义 Migration new Migration(1, 2) { ... }
执行 SQL 修改表结构 database.execSQL("ALTER TABLE users ADD COLUMN email TEXT")
注册迁移 .addMigrations(MIGRATION_1_2)
处理破坏性变更 .fallbackToDestructiveMigration()
通过正确实现迁移,可以确保 用户数据在数据库升级时不会丢失,同时支持复杂的表结构变更。
429

被折叠的 条评论
为什么被折叠?



