Миграция базы данных объекта, если добавлена только новая таблица

Предположим, у меня есть простая База данных:

@Database(entities = {User.class}, version = 1)
abstract class AppDatabase extends RoomDatabase {
    public abstract Dao getDao();
}

Теперь я добавляю новый объект: Pet и bumping version to 2:

@Database(entities = {User.class, Pet.class}, version = 2)
abstract class AppDatabase extends RoomDatabase {
    public abstract Dao getDao();
}

Конечно, Room выбрасывает исключение: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

Предполагая, что я не изменил класс User (так что все данные безопасны), я должен предоставить миграцию, которая просто создает новую таблицу. Итак, я изучаю классы, созданные Room, ищем сгенерированный запрос для создания моей новой таблицы, копирования и вставки в перенос:

final Migration MIGRATION_1_2 =
        new Migration(1, 2) {
            @Override
            public void migrate(@NonNull final SupportSQLiteDatabase database) {
                database.execSQL("CREATE TABLE IF NOT EXISTS 'Pet' ('name' TEXT NOT NULL, PRIMARY KEY('name'))");
            }
        };

Однако мне неудобно делать это вручную. Есть ли способ рассказать номер: я не касаюсь какой-либо существующей таблицы, поэтому данные безопасны. Пожалуйста, создайте миграцию для меня?

Ответы

Ответ 1

Комната НЕ имеет хорошей Миграционной Системы, по крайней мере, до 2.1.0-alpha03.

Ожидается, что лучшая система миграции в 2.2.0

Таким образом, пока у нас нет лучшей Миграционной системы, есть несколько обходных путей для легкой миграции в комнате.

Поскольку нет такого метода, как @Database(createNewTables = true) или MigrationSystem.createTable(User::class), который должен быть один или другой, единственный возможный способ - это запуск

CREATE TABLE IF NOT EXISTS 'User' ('id' INTEGER, PRIMARY KEY('id'))

внутри вашего метода migrate.

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE IF NOT EXISTS 'User' ('id' INTEGER, PRIMARY KEY('id'))")
    }
}

Для того, чтобы получить выше SQL-скрипт, у вас есть 4 способа

1. Пишите сами

По сути, вы должны написать вышеупомянутый скрипт, который будет соответствовать сценарию, который генерирует Room. Этот способ возможен, а не осуществим. (Считайте, что у вас есть 50 полей)

2. Экспортная схема

Если вы exportSchema = true в аннотацию @Database, Room сгенерирует схему базы данных в /@Database папки вашего проекта. Использование

@Database(entities = [User::class], version = 2, exportSchema = true)
abstract class AppDatabase : RoomDatabase {
   //...
}

Убедитесь, что вы включили следующие строки в build.grade вашего модуля приложения

kapt {
    arguments {
        arg("room.schemaLocation", "$projectDir/schemas".toString())
    }
} 

Когда вы запустите или 2.json проект, вы получите файл JSON 2.json, в котором есть все запросы в вашей базе данных Room.

  "formatVersion": 1,
  "database": {
    "version": 2,
    "identityHash": "325bd539353db508c5248423a1c88c03",
    "entities": [
      {
        "tableName": "User",
        "createSql": "CREATE TABLE IF NOT EXISTS '${TABLE_NAME}' ('id' INTEGER NOT NULL, PRIMARY KEY('id'))",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },

Таким образом, вы можете включить вышеупомянутый createSql в свой метод migrate.

3. Получить запрос из AppDatabase_Impl

Если вы не хотите экспортировать схему, вы все равно можете получить запрос, запустив или AppDatabase_Impl.java проект, который сгенерирует файл AppDatabase_Impl.java. и в указанном файле вы можете иметь.

@Override
public void createAllTables(SupportSQLiteDatabase _db) {
  _db.execSQL("CREATE TABLE IF NOT EXISTS 'User' ('id' INTEGER, PRIMARY KEY('id'))");

В методе createAllTables будут скрипты создания всех сущностей. Вы можете получить его и включить в свой метод migrate.

4. Обработка аннотаций.

Как вы можете догадаться, Room генерирует все вышеупомянутые schema и файлы AppDatabase_Impl во время компиляции и с обработкой аннотаций, которую вы добавляете с помощью

kapt "androidx.room:room-compiler:$room_version"

Это означает, что вы также можете сделать то же самое и создать свою собственную библиотеку обработки аннотаций, которая генерирует все необходимые для вас запросы на создание.

Идея состоит в том, чтобы создать библиотеку обработки аннотаций для комнатных аннотаций @Entity и @Database. Возьмем, к примеру, класс с аннотацией @Entity. Вот шаги, которые вы должны будете выполнить

  1. Создайте новый StringBuilder и добавьте "СОЗДАЙТЕ ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ"
  2. Получить имя таблицы или из class.simplename или tableName области @Entity. Добавьте его в свой StringBuilder
  3. Затем для каждого поля вашего класса создайте столбцы SQL. Возьмите имя, тип, обнуляемость поля либо самим полем, либо аннотацией @ColumnInfo. Для каждого поля необходимо добавить стиль столбца id INTEGER NOT NULL в ваш StringBuilder.
  4. Добавить первичные ключи от @PrimaryKey
  5. Добавьте ForeignKey и Indices если существует.
  6. После окончания конвертируйте его в строку и сохраните в каком-то новом классе, который вы хотите использовать. Например, сохраните как ниже
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

Затем вы можете использовать его как

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(UserSqlUtils().createTable)
    }
}

Я сделал для себя такую библиотеку, которую вы можете проверить и даже использовать в своем проекте. Обратите внимание, что библиотека, которую я сделал, не полная, и она просто соответствует моим требованиям для создания таблиц.

RoomExtension для лучшей миграции

Приложение, которое использует RoomExtension

Надеюсь, это было полезно.

Ответ 2

Извините, Room не поддерживает автоматическое создание таблиц без потери данных.

Обязательно писать миграцию. В противном случае он удалит все данные и создаст новую структуру таблицы.

Ответ 3

Вы можете добавить следующую команду gradle в свой defaultConfig в свой app.gradle:

javaCompileOptions {
        annotationProcessorOptions {
            arguments = ["room.schemaLocation":
                                 "$projectDir/schemas".toString()]
        }
    }

Когда вы запустите это, он скомпилирует список имен таблиц со своими соответствующими операциями CREATE TABLE, из которых вы можете просто скопировать и вставить в объекты миграции. Возможно, вам придется изменить имена таблиц.

Например, это из моей сгенерированной схемы:

"tableName": "assets",
"createSql": "CREATE TABLE IF NOT EXISTS '${TABLE_NAME}' ('asset_id' INTEGER NOT NULL, 'type' INTEGER NOT NULL, 'base' TEXT NOT NULL, 'name' TEXT NOT NULL, PRIMARY KEY('asset_id'))"

И поэтому я копирую вставку оператора createSql и изменяю значение "$ {TABLE_NAME}" на "активы" имя таблицы, а также создаваемые операторы создания комнаты Voila.

Ответ 4

Может быть, в этом случае (если вы только создали новую таблицу без изменения других), вы можете сделать это, не создавая никаких миграций вообще?

Ответ 5

Вы можете сделать это way-

@Database(entities = {User.class, Pet.class}, version = 2)

abstract class AppDatabase extends RoomDatabase {
public abstract Dao getDao();
public abstract Dao getPetDao();
}

Остальные будут такими же, как вы упомянули above-

 db = Room.databaseBuilder(this, AppDatabase::class.java, "your_db")
        .addMigrations(MIGRATION_1_2).build()

Ссылка - для получения дополнительной информации

Ответ 6

В этом случае вам не нужно выполнять миграцию, вы можете вызвать.fallbackToDestructiveMigration(), когда вы создаете экземпляр базы данных.

Пример:

    instance = Room.databaseBuilder(context, AppDatabase.class, "database name").fallbackToDestructiveMigration().build();

И не забудьте изменить версию базы данных.