Перенос базы данных комнаты, если добавлена ​​только новая таблица

107

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

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

Теперь я добавляю новую сущность: Petи увеличиваю версию до 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`))");
            }
        };

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

Петр Александр Хмелёвский
источник
Вы нашли решение этого?
Миккель Ларсен
3
У меня была такая же проблема, и я исправил ее так же, как и вы, и тоже не нашел решения. Рад, что я не одинок. :)
Миккель Ларсен
3
Тоже самое. Мне очень неудобно, что комната может генерировать запрос на создание внутри database_impl, но не может просто создать таблицу после начала миграции ....
JacksOnF1re
1
Я бы так много отдал за такую ​​фичу ... Также было бы неплохо смешать миграции и резервный механизм ...
Appyx
3
Я не уверен, что это будет полезно, но в Room есть возможность экспортировать схему базы данных в файл JSON. developer.android.com/training/data-storage/room/… Очевидно, это все равно будет означать добавление сценария миграции вручную, но вам не нужно будет выполнять маршрутизацию через автоматически сгенерированные классы, чтобы получить оператор SQL.
Джеймс Лендрем

Ответы:

84

Номер не делает НЕ есть хорошо миграционная система, по крайней мере, пока 2.1.0-alpha03.

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

Поскольку не существует такого метода, как @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 создаст схему базы данных в / schemas папки вашего проекта. Использование

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

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

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

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

  "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файл. и в пределах указанного файла вы можете иметь.

@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.1.0-alpha03и когда я написал разработчикам по электронной почте, я получил ответ

Ожидается, что в 2.2.0

К сожалению, у нас все еще отсутствует улучшенная система миграции.

Musooff
источник
3
Не могли бы вы указать, где вы читали, что для Room 2.2.x будет лучше миграция? Я не могу найти ничего, что делало бы это утверждение, и поскольку в настоящее время мы работаем над бета-версией 2.1.0, то, что находится в 2.2.0, кажется, пока неизвестно.
jkane001
1
@ jkane001 Я написал письмо одному из разработчиков комнаты и получил такой ответ. Нет такого публичного уведомления о 2.2.x (пока?)
musooff
5
В настоящее время используется версия 2.2.2, и до сих пор нет лучшей миграции :( Тем не менее, это отличный ответ и сэкономил мне массу работы, так что +1 за это.
smitty1
@androiddeveloper Все, кроме №4, который является обработкой аннотаций
musooff
1
@musooff Я действительно думаю, что можно добавить создание таблицы. Это самый безопасный способ скопировать код из функции createAllTables.
разработчик Android
5

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

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

Вишванат Кумар Санду
источник
0

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

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

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

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

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

Ссылка - Подробнее

Суджит Кумар
источник
0

Вы можете добавить следующую команду 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}" на "assets" имя таблицы и вуаля автоматически сгенерированные операторы создания комнаты.

Ларри Стент
источник
-1

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

Пример:

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

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

рудичован
источник
Это решение удалит все мои данные из существующих таблиц.
Петр Александр Хмелёвский
К сожалению, да. «Вы можете вызвать этот метод, чтобы изменить это поведение, чтобы воссоздать базу данных вместо сбоя. Обратите внимание, что это приведет к удалению всех данных в таблицах базы данных, которыми управляет Room».
rudicjovan
-2

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

user1730694
источник
1
Нет, в этом случае комната добавляет в журналы: java.lang.IllegalStateException: Необходим переход с {old_version} на {new_version}
Макс Макейчик