Міграція бази даних кімнати, якщо додано лише нову таблицю


99

Не будемо припускати, що у мене є проста база даних 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();
}

Звичайно, Кім видає виняток: 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`))");
            }
        };

Однак мені здається незручним робити це вручну. Чи є спосіб сказати Кімната: я не торкаюся жодної з існуючих таблиць, тому дані в безпеці. Будь ласка, створіть міграцію для мене?


Ви знайшли рішення цього?
Міккель Ларсен

3
У мене була та сама проблема, і я виправив її так само, як і ви, і я не знайшов рішення. Радий, що я тоді не один. :)
Міккель Ларсен

3
Те ж саме. Я вважаю дуже незручним те, що кімната може генерувати запит створення всередині database_impl, але не може просто створити таблицю, як тільки почнеться міграція ....
JacksOnF1re

1
Я б дав стільки за таку функцію ... Також було б непогано
поєднати

3
Я не впевнений, що це було б корисно, але Room має можливість експортувати схему бази даних у файл JSON. developer.android.com/training/data-storage/room/ ... Очевидно, це все одно означало б вручну додавати сценарій міграції, але вам не потрібно було б прокладати маршрут через автоматично згенеровані класи, щоб отримати свій оператор SQL.
Джеймс Лендрем,

Відповіді:


76

Номер дійсно НЕ є добре Міграційна система, по крайней мере, до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 згенерує схему бази даних в межах / схем папки вашого проекту. Використання є

@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, який містить усі запити у вашій базі даних 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файл. і в межах вказаного файлу, який ви можете мати.

@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

Сподіваюся, це було корисно.

ОНОВЛЕННЯ

На момент написання цієї відповіді була версія кімнати, 2.1.0-alpha03і коли я надіслав електронною поштою розробникам, отримав відповідь

Очікується, що в Україні буде покращена система міграції 2.2.0

На жаль, нам все ще бракує кращої системи міграції.


3
Чи можете ви вказати, де ви читали, що кімната 2.2.x матиме кращу міграцію? Я не можу знайти нічого, що заявляє це твердження, і оскільки ми зараз працюємо над бета-версією 2.1.0, те, що є в 2.2.0, на даний момент здається невідомим.
jkane001

1
@ jkane001 Я надіслав електронного листа одному з розробників кімнат і отримав відповідь. Немає такого публічного повідомлення щодо 2.2.x (ще?)
musooff

4
На даний момент у версії 2.2.2 і все ще не краща міграція :( Однак це відмінна відповідь і врятувала мені
купу

@androiddeveloper Усі, крім # 4, що
обробляє

1
@musooff Я насправді вважаю, що це нормально додати створення таблиці. Це найбезпечніший спосіб скопіювати код із функції "createAllTables".
розробник android

3

На жаль, 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}' на 'активи' ім'я таблиці, і автоматично створюю оператори створення кімнати voila.


-1

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

Приклад:

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

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


Це рішення видалить усі мої дані з існуючих таблиць.
Piotr Aleksander Chmielowski

На жаль, так. "Ви можете викликати цей метод, щоб змінити цю поведінку для повторного створення бази даних замість збою. Зверніть увагу, що це призведе до видалення всіх даних у таблицях баз даних, якими керує Room."
Rudicjovan

-2

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


1
Ні, у цьому випадку кімната кидає журнали: java.lang.IllegalStateException: необхідна міграція зі {old_version} на {new_version}
Макс Макейчик
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.