android 从数据库中删除房间实体

ckx4rj1h  于 2023-04-04  发布在  Android
关注(0)|答案(1)|浏览(86)

在这个实体的14个版本之后,它被决定删除它,因为它不再需要。什么是最安全的方法来删除这个实体而不影响以前使用它的版本?
我们有手动迁移与此实体,我删除了他们,因为他们不再需要,但有一个较旧的版本安装,并更新到这一个是破坏应用程序。是否有更多的东西需要做的工作?

4nkexdtk

4nkexdtk1#

您不能简单地删除迁移。如果方案已更改,则Room会检测到这一点,并且需要迁移,但如果在删除所有表时使用fallbackToDestructiveMigration,则会删除所有数据,然后根据预期的方案创建表,则会出现例外情况。
Room通过将编译时构建的模式的散列与存储在room_master_table中的散列进行比较来检测模式的更改。如果模式已更改,则散列也将更改。
如果散列已更改,则Room将检查版本号是否已更改,如果未更改,则将出现失败,因为需要迁移,但版本号未增加。
如果版本号增加了,那么Room将检查是否有一个Migration包含了从旧版本号到新版本号的更改,除非指定了对fallbackToDestructiveMigration()的调用,在这种情况下,Room将调用编译时生成的dropAllTables方法。
如果您要删除实体,并且希望保留数据,则必须进行迁移(如果您希望保留现有数据)。迁移应DROP(但是,由于Room只关心它所期望的存在,因此如果这是对架构的唯一更改,则可以进行不做任何操作的迁移,并且忽略表)

演示

以下是对上述一些核心方面的简单演示。
首先是@Entity注解的类(2)

MainTable表示要与数据沿着保存的表:-

@Entity
class MainTable {
    @PrimaryKey
    Long id=null;
    String name;

    public MainTable(){}
    public MainTable(Long id, String name) {
        this.id=id;
        this.name=name;
    }
    public MainTable(String name) {
        this.name=name;
    }
}

OtherTable要删除的表

@Entity
class OtherTable {
    @PrimaryKey
    Long otherId=null;
    String otherName;
}

AllDAODAO的接口:-

@Dao
interface AllDAOs {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long insert(MainTable mainTable);
    /* Not necessary as not used in demo but would likely exist before removal of OtherTable
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long insert(OtherTable otherTable);

     */

    @Query("SELECT * FROM maintable")
    List<MainTable> getAllFromMainTable();
    /* Not necessary as not used in demo but would likely exist before removal of OtherTable
    @Query("SELECT * FROM othertable")
    List<OtherTable> getAllFromOtherTable();

     */
}
  • OtherTable DAO被注解掉,因为它们不会在演示中使用(包括在内只是为了彻底检查)
    TheDatabase@Database注解类:-
@Database(
        entities = {
                MainTable.class
                , OtherTable.class /*<<<<<<<<<< WILL BE REMOVED for V15 */
        },
        exportSchema = false,
        version = TheDatabase.DATABASE_VERSION
)
abstract class TheDatabase extends RoomDatabase {
    public final static String DATABASE_NAME = "the_database.db";
    public final static int DATABASE_VERSION = 14;

    abstract AllDAOs getAllDAOs();

    private static TheDatabase instance;
    public static TheDatabase getInstance(Context context) {
        if (instance==null) {
            instance = Room.databaseBuilder(context,TheDatabase.class,DATABASE_NAME)
                    .allowMainThreadQueries()
                    .addMigrations(mig14to15) /*<<<<<<<<<< The migration can be left in if not invoked */
                    .build();
        }
        return instance;
    }

    private static final Migration mig14to15 = new Migration(14,15) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {

        }
    };
}

MainActivity使用数据库的Activity代码

public class MainActivity extends AppCompatActivity {

    TheDatabase db;
    AllDAOs dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = TheDatabase.getInstance(this);
        dao = db.getAllDAOs();
        if (TheDatabase.DATABASE_VERSION <= 14) populateMainTable();
        for(MainTable mt: dao.getAllFromMainTable()) {
            Log.d("DBINFO_V" + TheDatabase.DATABASE_VERSION,"MainTable row is " + mt.name);
        }
        logSchema(db);
    }

    private void populateMainTable() {
        dao.insert(new MainTable(1000L,"Name1"));
        dao.insert(new MainTable("Name2"));
        MainTable name3 = new MainTable();
        name3.name = "Name3";
        dao.insert(name3);
    }

    @SuppressLint("Range")
    private void logSchema(TheDatabase db) {
        SupportSQLiteDatabase sdb = db.getOpenHelper().getWritableDatabase();
        Cursor csr = db.query(new SimpleSQLiteQuery("SELECT * FROM sqlite_master"));
        while (csr.moveToNext()) {
            Log.d(
                    "LOGSCHEMA_V" + TheDatabase.DATABASE_VERSION,
                    "Component is " + csr.getString(csr.getColumnIndex("name"))
                    + " Type is " + csr.getString(csr.getColumnIndex("type"))
            );
        }
    }
}
  • 这将:
  • 示例化数据库和DAO
  • 如果版本为14或更低版本,则添加一些数据
  • 从MainTable中提取数据并将其写入日志。
  • 从sqlite_master中提取模式,将定义的组件(表、索引、视图等)写入日志,以准确显示数据库中的内容(与Room对数据库中存在的内容的想法相反)
    测试1

当上面被编译时,这将是在删除表之前运行的。作为编译的一部分,如上所述,生成哈希。这个哈希可以在生成的java中看到,例如。

db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a9dc621848cb6cf03f2ce9a3e93ee987')");

当应用程序运行时,日志包括:

2023-03-31 11:46:28.521 D/DBINFO_V14: MainTable row is Name1
2023-03-31 11:46:28.521 D/DBINFO_V14: MainTable row is Name2
2023-03-31 11:46:28.521 D/DBINFO_V14: MainTable row is Name3

2023-03-31 11:46:28.522 D/LOGSCHEMA_V14: Component is android_metadata Type is table
2023-03-31 11:46:28.522 D/LOGSCHEMA_V14: Component is MainTable Type is table
2023-03-31 11:46:28.522 D/LOGSCHEMA_V14: Component is OtherTable Type is table
2023-03-31 11:46:28.522 D/LOGSCHEMA_V14: Component is room_master_table Type is table
  • 即,数据库中存在3行,并且存在4个表:
    *android_metadata(以及存储locale的android API特定表)
    *MainTableOtherTable按房间
    *room_master_table哪个房间用来存储hash。

如果使用应用程序检查,则:-

  • 请注意,散列已被存储。
    阶段2删除OtherTable

不是删除类和其他部分,而是删除实体列表以排除OtherTable.class。此外,为了模拟无迁移情况,.AddMigrations方法调用被注解掉。因此:

@Database(
        entities = {
                MainTable.class
                /*, OtherTable.class */ /*<<<<<<<<<< WILL BE REMOVED for V15 */
        },
        exportSchema = false,
        version = TheDatabase.DATABASE_VERSION
)
abstract class TheDatabase extends RoomDatabase {
    public final static String DATABASE_NAME = "the_database.db";
    public final static int DATABASE_VERSION = 14;

    abstract AllDAOs getAllDAOs();

    private static TheDatabase instance;
    public static TheDatabase getInstance(Context context) {
        if (instance==null) {
            instance = Room.databaseBuilder(context,TheDatabase.class,DATABASE_NAME)
                    .allowMainThreadQueries()
                    //.addMigrations(mig14to15) /*<<<<<<<<<< The migration can be left in if not invoked */
                    .build();
        }
        return instance;
    }

    private static final Migration mig14to15 = new Migration(14,15) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {

        }
    };
}

编译项目后,生成的java哈希值为'73c7fb7c7251909ab317f9dbc9309a80',与之前的哈希值明显不同。
应用程序运行并失败,出现以下情况:-

java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. ....

所以很明显,Room在运行时已经看到模式发生了变化(存储和编译的哈希不匹配)。

阶段3将版本号增加到15

因此代码被更改为使用public final static int DATABASE_VERSION = 15;并重新编译。
哈希现在是'73c7fb7c7251909ab317f9dbc9309a80',即版本号更改不会更改哈希。
所以当应用程序再次运行时,这次又失败了:

java.lang.IllegalStateException: A migration from 14 to 15 was required but not found.

因此,需要迁移(请注意,故意不尝试.fallbackToDestructiveMigration(),因为它不会保留数据)。

阶段4通过取消注解.addMigrations调用来引入dummy.noop迁移,即:-

instance = Room.databaseBuilder(context,TheDatabase.class,DATABASE_NAME)
                .allowMainThreadQueries()
                .addMigrations(mig14to15) /*<<<<<<<<<< The migration can be left in if not invoked */
                .build();

现在运行:-
日志包括:-

2023-03-31 12:05:37.430 D/DBINFO_V15: MainTable row is Name1
2023-03-31 12:05:37.430 D/DBINFO_V15: MainTable row is Name2
2023-03-31 12:05:37.430 D/DBINFO_V15: MainTable row is Name3

2023-03-31 12:05:37.431 D/LOGSCHEMA_V15: Component is android_metadata Type is table
2023-03-31 12:05:37.431 D/LOGSCHEMA_V15: Component is MainTable Type is table
2023-03-31 12:05:37.431 D/LOGSCHEMA_V15: Component is OtherTable Type is table
2023-03-31 12:05:37.431 D/LOGSCHEMA_V15: Component is room_master_table Type is table
  • 所以版本15和所有的表都保留下来,所以Room不关心rouge OtherTable是否存在,即使它不使用或不需要它。

如果应用程序被卸载,然后重复上述步骤,但迁移更改为:-

private static final Migration mig14to15 = new Migration(14,15) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
        supportSQLiteDatabase.execSQL("DROP TABLE IF EXISTS othertable");
    }
};

那么最后一步的日志包括:

2023-03-31 12:12:10.955 D/DBINFO_V15: MainTable row is Name1
2023-03-31 12:12:10.955 D/DBINFO_V15: MainTable row is Name2
2023-03-31 12:12:10.955 D/DBINFO_V15: MainTable row is Name3

2023-03-31 12:12:10.956 D/LOGSCHEMA_V15: Component is android_metadata Type is table
2023-03-31 12:12:10.956 D/LOGSCHEMA_V15: Component is MainTable Type is table
2023-03-31 12:12:10.956 D/LOGSCHEMA_V15: Component is room_master_table Type is table

即OtherTable已被删除,Room不关心,因为它不是必需的。

相关问题