Android Room-迁移数据库时合并用户的android room数据库

问题描述 投票:0回答:1

我有一个会议室数据库,该数据库及其预加载的数据存储在资产/数据库中。我正在创建一个更新的版本,其中包含数据库中更多的内容,以供下次更新。

当前,如果我在不更改架构的情况下将新内容添加到数据库并重新安装该应用程序,则不会显示这些新内容。我看到更改的唯一方法是卸载并重新安装该应用程序。但是我需要将具有数据库的用户数据与新内容合并,因为我需要获取用户的“收藏夹”,这是具有项目内容的表的整数列。

这可能吗?

这就是我创建数据库的方式。

public static AppDatabase getInMemoryDatabase(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "app_database.db")
                            .createFromAsset("database/QuotesDB.db")
                            .addMigrations(MIGRATION_1_2)
                            .build();
                }
            }
        }
        return INSTANCE;
    }

我尝试使用以下代码进行迁移,但仍无法更新内容。

/**
     * Migrate from:
     * version 1 - initial contents.
     * to
     * version 2 - updated database contents (no schema changes)
     */
    @VisibleForTesting
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            // I need to tell Room that it should use the data
            // from version 1 ( with the user's favorites ) to version 2.
        }
    };
java database android-room database-migration
1个回答
0
投票

这可能吗?是。但是,这有点复杂。

简而言之,实际上您可以反过来做。而不是使用资产中的新数据库并尝试检索以前的数据(如果使用“房间迁移”,则很麻烦,因为您必须有效地交换到新创建/复制的数据库,因为迁移时您在事务中,所以这将变得更加复杂) 。

但是,如果您对使用中的数据库而不是资产数据库进行模式更改,则可以获取资产数据库并复制新的非用户数据(如果用户的数据与非数据库混合在一起,将会非常复杂。用户数据)。

即使这不是那么简单。但是,这是一个简单的示例/ scanario,它基于将代码稍微扩展为:-

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase db) {
        final String TAG = "MIGRATE_1_2";
        Log.d(TAG,"Database Version when called is " + db.getVersion());

        // I need to tell Room that it should use the data
        // from version 1 ( with the user's favorites ) to version 2.

        // "CREATE TABLE IF NOT EXISTS `userdata` (`userId` INTEGER DEFAULT uid, `name` TEXT, PRIMARY KEY(`userId`))"
        //db.execSQL("CREATE TABLE IF NOT EXISTS `userdata_saveuserdata` (`userId` INTEGER, `name` TEXT, PRIMARY KEY(`userId`))");
        //db.execSQL("INSERT INTO `userdata_saveuserdata` SELECT * FROM `userdata`");
        db.execSQL("ALTER TABLE `otherdata` ADD COLUMN `column2` TEXT");
        Log.d(TAG,"Checking Context");
        if (sContext != null) {
            applyAssetDB(db);
        } else {
            Log.d(TAG,"Context is null!!!!");
        }
    }
};

您可以看到,这通过添加新列来更改了其他数据表(而不是用户表)。

然后检查sContext是否不为空。

  • 使用有效上下文,而不是硬编码路径。

然后调用applyAssetDB,它是:-

private static void applyAssetDB(SupportSQLiteDatabase sdb) {
    String TAG = "APPLYASSETDB";
    String mainDatabaseName = (new File(sdb.getPath()).getName());
    String assetDatabaseName = mainDatabaseName + "_asset";
    String asset_schema = "asset_schema";
    Log.d(TAG,"Attempting application of asset data to database."
                    + "\n\tActual Database = " + mainDatabaseName
                    + "\n\tAsset Database will be " + assetDatabaseName
                    + "\n\tSchema for attached database will be  " + asset_schema
            );
    copyDatabaseFromAssets(AppDatabase.sContext,MainActivity.ASSETNAME,assetDatabaseName);
    /*
    if (sdb.isWriteAheadLoggingEnabled()) {
        setAssetDBToWALMode(sContext.getDatabasePath(assetDatabaseName).getPath());
    }
    Log.d(TAG,"Attempting to ATTACH  asset database " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
    sdb.execSQL("ATTACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "' AS " + asset_schema);
    Log.d(TAG,"Attempting INSERTING NEW DATA using\n\t" + "INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema);
    sdb.execSQL("INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema);
    Log.d(TAG,"Attempting to DETACH " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
    sdb.execSQL("DETACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
    */

    int insertRows = 0;
    SQLiteDatabase assetdb = SQLiteDatabase.openDatabase(sContext.getDatabasePath(assetDatabaseName).getPath(),null,SQLiteDatabase.OPEN_READONLY);
    Cursor assetCursor = assetdb.query("`otherdata`",null,null,null,null,null,null);
    ContentValues cv = new ContentValues();
    while (assetCursor.moveToNext()) {
        cv.clear();
        for (String c: assetCursor.getColumnNames()) {
            if (assetCursor.getType(assetCursor.getColumnIndex(c)) == Cursor.FIELD_TYPE_BLOB) {
                cv.put(c,assetCursor.getBlob(assetCursor.getColumnIndex(c)));
            } else {
                cv.put(c,assetCursor.getString(assetCursor.getColumnIndex(c)));
            }
        }
        if (sdb.insert("`otherdata`", OnConflictStrategy.IGNORE,cv) > 0 ) insertRows++;
    }
    Log.d(TAG,"Inserted " + insertRows + " from the Asset Database");
    assetCursor.close();

    Log.d(TAG,"Deleting " + sContext.getDatabasePath(assetDatabaseName).getPath());
    if ((new File(sContext.getDatabasePath(assetDatabaseName).getPath())).delete()) {
        Log.d(TAG,"Copied AssetDatabase successfully deleted.");
    } else {
        Log.d(TAG,"Copied Asset Database file not deleted????");
    }
    Log.d(TAG,"Finished");
}
  • 在尝试附加从资产复制的数据库时遇到了有意注释掉的代码问题,因此恢复为使用单独的连接。

这将通过copyDatabaseFromAssets方法将数据库从资产复制到默认数据库位置(如下所示)。它从资产数据库中提取所有非用户数据,并将其插入到OnConflictStrategy.IGNORE上的原始数据库(但根据更改的架构进行了更改)中,仅插入新行。 userdata表保持不变,因此用户数据被重新设置。

    显然,这不能满足更改的行。

  • 这里是

    copyDatabaseFromAssetsprivate static void copyDatabaseFromAssets(Context context, String assetName, String databaseName) { String TAG = "COPYDBFROMASSET"; int bufferSize = 1024 * 4, length = 0, read = 0, written = 0, chunks = 0; byte[] buffer = new byte[bufferSize]; try { Log.d(TAG,"Attempting opening asset " + assetName + " as an InputFileStream."); InputStream is = context.getAssets().open(assetName); Log.d(TAG,"Attempting opening FileOutputStream " + context.getDatabasePath(databaseName).getPath()); OutputStream os = new FileOutputStream(context.getDatabasePath(databaseName)); Log.d(TAG,"Initiating copy."); while((length = is.read(buffer)) > 0) { read += length; os.write(buffer,0,length); written += length; chunks++; } Log.d(TAG,"Read " + read + "bytes; Wrote " + written + " bytes; in " + chunks); Log.d(TAG,"Finalising (Flush and Close output and close input)"); os.flush(); os.close(); is.close(); Log.d(TAG,"Finalised"); } catch (IOException e) { throw new RuntimeException("Error copying Database from Asset " + e.getMessage()); } }

    这里是一个示例活动

    MainActivity

    ,将所有这些放在一起(注意,为了方便起见,我使用allowMainThreadQueries):-public class MainActivity extends AppCompatActivity { //public static final int DBVERSION = 1; //!!!!! ORIGINAL public static final int DBVERSION = 2; public static final String DBNAME = "app_database.db"; public static final String ASSETNAME = "database/QuotesDB.db"; AppDatabase appDB; AllDao adao; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); appDB.setContext(this); appDB = Room.databaseBuilder(this,AppDatabase.class,DBNAME) .allowMainThreadQueries() .createFromAsset(ASSETNAME) .addCallback(AppDatabase.CALLBACK) .addMigrations(AppDatabase.MIGRATION_1_2) .build(); adao = appDB.allDao(); appDB.logDBInfo(); if (adao.getUserDataRowCount() == 3) { adao.insertOneUserData(new UserData("ADDEDU100")); adao.insertOneUserData(new UserData("ADDEDU200")); adao.insertOneUserData(new UserData("ADDEDU300")); } appDB.logDBInfo(); } }
    运行时(在更改了新模式的相关代码并增加了版本之后,日志中的结果为:-

    2019-11-30 10:56:38.768 12944-12944/a.roommigrationwithassets D/MIGRATE_1_2: Database Version when called is 1 2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/MIGRATE_1_2: Checking Context 2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Attempting application of asset data to database. Actual Database = app_database.db Asset Database will be app_database.db_asset Schema for attached database will be asset_schema 2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Attempting opening asset database/QuotesDB.db as an InputFileStream. 2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Attempting opening FileOutputStream /data/user/0/a.roommigrationwithassets/databases/app_database.db_asset 2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Initiating copy. 2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Read 12288bytes; Wrote 12288 bytes; in 3 2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Finalising (Flush and Close output and close input) 2019-11-30 10:56:38.772 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Finalised 2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Inserted 3 from the Asset Database 2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Deleting /data/user/0/a.roommigrationwithassets/databases/app_database.db_asset 2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Copied AssetDatabase successfully deleted. 2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Finished 2019-11-30 10:56:38.815 12944-12944/a.roommigrationwithassets D/ONOPEN: Database Version when called is 2 2019-11-30 10:56:38.816 12944-12944/a.roommigrationwithassets D/ONOPEN: Database Version after Super call is 2 2019-11-30 10:56:38.819 12944-12944/a.roommigrationwithassets D/DBINFO: UserData rowcount = 6 ID = 1 NAME = OU1 ID = 2 NAME = OU2 ID = 3 NAME = OU3 ID = 4 NAME = ADDEDU100 ID = 5 NAME = ADDEDU200 ID = 6 NAME = ADDEDU300 OtherData rowcount = 3 ID = 1Column1 = OD1 ID = 2Column1 = OD2 ID = 3Column1 = OD3 2019-11-30 10:56:38.821 12944-12944/a.roommigrationwithassets D/DBINFO: UserData rowcount = 6 ID = 1 NAME = OU1 ID = 2 NAME = OU2 ID = 3 NAME = OU3 ID = 4 NAME = ADDEDU100 ID = 5 NAME = ADDEDU200 ID = 6 NAME = ADDEDU300 OtherData rowcount = 3 ID = 1Column1 = OD1 ID = 2Column1 = OD2 ID = 3Column1 = OD3

    AppDatabase

    类的完整代码(请注意,其中包括一些冗余代码)是:-
    @Database(version = MainActivity.DBVERSION, exportSchema = false,entities = {UserData.class,OtherData.class}) abstract class AppDatabase extends RoomDatabase { abstract AllDao allDao(); static Context sContext; static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase db) { final String TAG = "MIGRATE_1_2"; Log.d(TAG,"Database Version when called is " + db.getVersion()); // I need to tell Room that it should use the data // from version 1 ( with the user's favorites ) to version 2. // "CREATE TABLE IF NOT EXISTS `userdata` (`userId` INTEGER DEFAULT uid, `name` TEXT, PRIMARY KEY(`userId`))" //db.execSQL("CREATE TABLE IF NOT EXISTS `userdata_saveuserdata` (`userId` INTEGER, `name` TEXT, PRIMARY KEY(`userId`))"); //db.execSQL("INSERT INTO `userdata_saveuserdata` SELECT * FROM `userdata`"); db.execSQL("ALTER TABLE `otherdata` ADD COLUMN `column2` TEXT"); Log.d(TAG,"Checking Context"); if (sContext != null) { applyAssetDB(db); } else { Log.d(TAG,"Context is null!!!!"); } } }; static final RoomDatabase.Callback CALLBACK = new RoomDatabase.Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { Log.d("ONCREATE","Database Version when called is " + db.getVersion()); super.onCreate(db); Log.d("ONCREATE","Database Version after Super call is " + db.getVersion()); } @Override public void onOpen(@NonNull SupportSQLiteDatabase db) { Log.d("ONOPEN","Database Version when called is " + db.getVersion()); super.onOpen(db); Log.d("ONOPEN","Database Version after Super call is " + db.getVersion()); } @Override public void onDestructiveMigration(@NonNull SupportSQLiteDatabase db) { Log.d("ONDESTRMIG","Database Version when called is " + db.getVersion()); super.onDestructiveMigration(db); Log.d("ONDESTRMIG","Database Version after Super call is " + db.getVersion()); } }; public void logDBInfo() { AllDao adao = this.allDao(); List<UserData> allUserDataRows = adao.getAllUserDataRows(); StringBuilder sb = new StringBuilder().append("UserData rowcount = ").append(allUserDataRows.size()); for (UserData u: allUserDataRows) { sb.append("\n\tID = ").append(u.getId()).append(" NAME = " + u.getName()); } List<OtherData> allOtherDataRows = adao.getAllOtherDataRows(); sb.append("\n\nOtherData rowcount = ").append(allOtherDataRows.size()); for (OtherData o: allOtherDataRows) { sb.append("\n\tID = ").append(o.getOtherDataId()).append("Column1 = ").append(o.getColumn1()); } Log.d("DBINFO",sb.toString()); } static void setContext(Context context) { sContext = context; } private static void applyAssetDB(SupportSQLiteDatabase sdb) { String TAG = "APPLYASSETDB"; String mainDatabaseName = (new File(sdb.getPath()).getName()); String assetDatabaseName = mainDatabaseName + "_asset"; String asset_schema = "asset_schema"; Log.d(TAG,"Attempting application of asset data to database." + "\n\tActual Database = " + mainDatabaseName + "\n\tAsset Database will be " + assetDatabaseName + "\n\tSchema for attached database will be " + asset_schema ); copyDatabaseFromAssets(AppDatabase.sContext,MainActivity.ASSETNAME,assetDatabaseName); /* if (sdb.isWriteAheadLoggingEnabled()) { setAssetDBToWALMode(sContext.getDatabasePath(assetDatabaseName).getPath()); } Log.d(TAG,"Attempting to ATTACH asset database " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema); sdb.execSQL("ATTACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "' AS " + asset_schema); Log.d(TAG,"Attempting INSERTING NEW DATA using\n\t" + "INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema); sdb.execSQL("INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema); Log.d(TAG,"Attempting to DETACH " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema); sdb.execSQL("DETACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema); */ int insertRows = 0; SQLiteDatabase assetdb = SQLiteDatabase.openDatabase(sContext.getDatabasePath(assetDatabaseName).getPath(),null,SQLiteDatabase.OPEN_READONLY); Cursor assetCursor = assetdb.query("`otherdata`",null,null,null,null,null,null); ContentValues cv = new ContentValues(); while (assetCursor.moveToNext()) { cv.clear(); for (String c: assetCursor.getColumnNames()) { if (assetCursor.getType(assetCursor.getColumnIndex(c)) == Cursor.FIELD_TYPE_BLOB) { cv.put(c,assetCursor.getBlob(assetCursor.getColumnIndex(c))); } else { cv.put(c,assetCursor.getString(assetCursor.getColumnIndex(c))); } } if (sdb.insert("`otherdata`", OnConflictStrategy.IGNORE,cv) > 0 ) insertRows++; } Log.d(TAG,"Inserted " + insertRows + " from the Asset Database"); assetCursor.close(); Log.d(TAG,"Deleting " + sContext.getDatabasePath(assetDatabaseName).getPath()); if ((new File(sContext.getDatabasePath(assetDatabaseName).getPath())).delete()) { Log.d(TAG,"Copied AssetDatabase successfully deleted."); } else { Log.d(TAG,"Copied Asset Database file not deleted????"); } Log.d(TAG,"Finished"); } private static void copyDatabaseFromAssets(Context context, String assetName, String databaseName) { String TAG = "COPYDBFROMASSET"; int bufferSize = 1024 * 4, length = 0, read = 0, written = 0, chunks = 0; byte[] buffer = new byte[bufferSize]; try { Log.d(TAG,"Attempting opening asset " + assetName + " as an InputFileStream."); InputStream is = context.getAssets().open(assetName); Log.d(TAG,"Attempting opening FileOutputStream " + context.getDatabasePath(databaseName).getPath()); OutputStream os = new FileOutputStream(context.getDatabasePath(databaseName)); Log.d(TAG,"Initiating copy."); while((length = is.read(buffer)) > 0) { read += length; os.write(buffer,0,length); written += length; chunks++; } Log.d(TAG,"Read " + read + "bytes; Wrote " + written + " bytes; in " + chunks); Log.d(TAG,"Finalising (Flush and Close output and close input)"); os.flush(); os.close(); is.close(); Log.d(TAG,"Finalised"); } catch (IOException e) { throw new RuntimeException("Error copying Database from Asset " + e.getMessage()); } } private static void setAssetDBToWALMode(String assetDBPath) { SQLiteDatabase db = SQLiteDatabase.openDatabase(assetDBPath,null,SQLiteDatabase.OPEN_READWRITE); db.enableWriteAheadLogging(); db.close(); } }
  • © www.soinside.com 2019 - 2024. All rights reserved.