Используя Room, я загружаю свою базу данных из ресурсов (уже заполненная база данных).
Я планирую очень часто обновлять содержимое базы данных, поэтому каждый раз, когда версия увеличивается, старая база данных, которую пользователь установил в своем хранилище, должна быть удалена и сгенерирована снова (простым копированием новой базы данных в хранилище).
Предполагается, что этот процесс будет выполняться автоматически комнатой при включении fallbackToDestructiveMigration()
. Однако я обнаружил, что при включении fallbackToDestructiveMigration()
вместе с createFromAsset()
каждый раз, когда запрашивается база данных, Room удаляет старую базу данных, даже если ее версия уже является последней, и создает ее снова, как если бы потребовалась миграция.
@Module
@InstallIn(SingletonComponent.class)
public class TestRoomModule {
private static final String DATABASE_NAME = "test.db";
@Provides
public TestDatabase provideDatabase(@ApplicationContext Context context) {
return Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build();
}
}
Нравится: эта проблема упоминается здесь
Решение, предложенное в предыдущем сообщении, просто удаляет «fallbackToDestructiveMigration», но это не решение, а обходной путь, поскольку если я обновлю свою базу данных и версию моей базы данных, приложение выдаст исключение, поскольку миграция не была предоставлена.
@Database(entities = {
version = 1)
public abstract class TestDatabase extends RoomDatabase {
Обновление до версии 2:
@Database(entities = {
version = 2)
public abstract class TestDatabase extends RoomDatabase {
Выкинет:
A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase
Как я могу это исправить, чтобы fallbackToDestructiveMigration()
срабатывал ТОЛЬКО тогда, когда версия базы данных действительно изменилась?
Итак, вы используете createFromAsset()
; это означает, что ваша база данных заполняется в активах, теперь какие именно изменения в базе данных вы делаете (это изменение строк? или изменение схемы БД, например добавление/удаление столбцов?). Это изменение со стороны разработчика или пользователя? Я думаю, что это сделано разработчиком и помещено в следующий выпуск приложения, я правильно понимаю?
@Zain Да, сделанные модификации могут быть как строками, так и структурой данных, и они могут быть выполнены только разработчиком.
Решение, предложенное предыдущим постом, просто удалить «fallbackToDestructiveMigration», но это не решение, а обходной путь, поскольку если я обновлю свою базу данных и версию моей базы данных, приложение выдаст исключение, поскольку миграция не была предоставлена.
Удаление fallbackToDestructiveMigration
решит проблему перезаписи/воссоздания базы данных при каждом запросе.
Но теперь вы не сможете выполнить миграцию базы данных в следующем выпуске, потому что вы не укажете план миграции с помощью addMigrations()
, иначе вы получите IllegalStateException
.
Попытка поймать это исключение потерпит неудачу, поскольку Room
этого не допустит, например:
@Provides
public TestDatabase provideDatabase(@ApplicationContext Context context) {
TestDatabase database;
try {
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.allowMainThreadQueries()
.build();
} catch (IllegalStateException e) {
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build();
}
return database;
}
Итак, поскольку у вас нет планов миграции с addMigrations()
, единственный способ - манипулировать с помощью fallbackToDestructiveMigration
каким-либо флагом, поэтому мы вызываем fallbackToDestructiveMigration
условно по необходимости.
Возможное решение состоит в том, чтобы управлять двумя целыми числами, одно из которых указывает на текущую версию базы данных, а другое — на новую версию базы данных. Либо SharedPrefs
, либо DataStore
являются типичными инструментами для этого:
Предполагая, что текущая версия базы данных равна 1, а в новой версии — 2:
Создайте SharedPrefs
вспомогательные методы:
static String SHARED_PREFS_NAME = "SHARED_PREFS_NAME";
static String CURRENT_DB_VERSION = "CURRENT_DB_VERSION";
static String NEW_DB_VERSION = "NEW_DB_VERSION";
public static int getCurrentDBVersion(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
return prefs.getInt(CURRENT_DB_VERSION , 1);
}
public static void setCurrentDBVersion(Context context, int version) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(CURRENT_DB_VERSION , version);
editor.apply();
}
public static int getNewDBVersion(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
return prefs.getInt(NEW_DB_VERSION, 1);
}
public static void setNewDBVersion(Context context, int version) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(NEW_DB_VERSION, version);
editor.apply();
}
Затем проверьте условие в классе базы данных:
@Database(entities = {...},
version = 2)
public abstract class TestDatabase extends RoomDatabase {
@Provides
public TestDatabase provideDatabase(@ApplicationContext Context context) {
TestDatabase database;
int newDBVersion = getNewDBVersion(context);
int currentDBVersion = getNewDBVersion(context);
if (newDBVersion == currentDBVersion) { // No migration required
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.allowMainThreadQueries()
.build();
} else { // Migration is needed (call fallbackToDestructiveMigration)
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build();
// set the current version to the new version as the migration is done.
setCurrentDBVersion(context, newDBVersion);
}
return database;
}
}
И всякий раз, когда у вас есть выпуск, требующий миграции базы данных, вы должны установить новую версию базы данных на новую версию, это будет в обратном вызове активности onCreate()
:
setNewDBVersion(this, 2);
Обязательно установите это перед доступом к базе данных.
Если в какой-то версии миграция не требуется, оставьте setNewDBVersion(this, 1)
текущую версию.
fallbackToDestructiveMigration()
Вместо того, чтобы хранить версии базы данных в каком-то хранилище (SharedPrefs/DataStore), мы бы проверили версию базы данных:
Цитата из этого ответа
Измененную (увеличенную) USER_VERSION можно обнаружить, просто обратившись файлы (текущая база данных и актив) и чтение первого 100 байт, а затем извлечь 4 байта по смещению 60 (68 для идентификатор приложения).
И этот ответ для этой цели ссылается на приведенный ниже метод:
private static boolean isNewAsset(Context context, String asset, String dbname) {
File current_Db = context.getDatabasePath(dbname);
if (!current_Db.exists()) return false; /* No Database then nothing to do */
int current_Db_version = getDBVersion(current_Db);
Log.d("DBINFO","isNewAsset has determined that the current database version is " + current_Db_version);
if (current_Db_version < 0) return false; /* No valid version */
int asssetVersion = getAssetVersion(context,asset);
Log.d("DBINFO","isNewAsset has determined that the asset version is " + asssetVersion);
if (asssetVersion > current_Db_version) {
Log.d("DBINFO","isNewAsset has found that the asset version is greater than the current db version " + current_Db_version);
return true;
} else {
Log.d("DBINFO","isNewAsset has found that the asset version is unchanged " + current_Db_version);
}
return false;
}
allowMainThreadQueries()
не следует использовать в рабочем приложении, его можно использовать только для тестирования и простоты.
Лично я бы предпочел добавить миграцию базы данных вместо использования fallbackToDestructiveMigration()
даже для предварительно заполненных баз данных; вероятно, поскольку последнее разрушительно :).
Если нет изменений схемы (таблиц/столбцов); т. е. просто нужно обновить записи базы данных (строки); мы можем просто иметь пустой план миграции и просто удалять базу данных при любом обновлении с помощью deleteDatabase() в контексте, чтобы обновить базу данных в активах:
@Database(entities = {...},
version = 2)
public abstract class TestDatabase extends RoomDatabase {
@Provides
public TestDatabase provideDatabase(@ApplicationContext Context context) {
int newDBVersion = getNewDBVersion(context);
int currentDBVersion = getNewDBVersion(context);
if (newDBVersion != currentDBVersion) { // Migration required
// delete the database to refresh it on the assets and remove any cached version
context.deleteDatabase("databases/test.db");
// set the current version to the new version as the migration is done.
setCurrentDBVersion(context, newDBVersion);
}
return Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.allowMainThreadQueries()
.addMigrations(migration_v1_v2)
.build();
}
// Empty Migration plan
static Migration migration_v1_v2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
Log.d("TAG","Empty Migration plan for data insertion, deletion, or update - Migration from V1 to V2");
}
};
}
Такой подробный и хороший ответ сработал как шарм!
Можете ли вы отредактировать свой вопрос, указав пример версий до и после (т. е. какие изменения, поскольку моя база данных может быть активом, это может быть существующая база данных или даже и то, и другое). Вы также можете просмотреть вопросы и ответы под тегом android-room-prepackageddatabase.