Migracja bazy danych pokoju, jeśli dodana jest tylko nowa tabela

107

Nie zakładajmy, że mam prostą bazę danych Pokoju:

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

Teraz dodaję nową jednostkę: Peti wbijam wersję na 2:

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

Oczywiście Room rzuca wyjątek: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

Zakładając, że nie zmieniłem Userklasy (więc wszystkie dane są bezpieczne), muszę zapewnić migrację, która po prostu tworzy nową tabelę. Tak więc patrzę na klasy generowane przez Room, szukam wygenerowanego zapytania, aby utworzyć nową tabelę, kopiując ją i wklejając do migracji:

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`))");
            }
        };

Jednak wykonanie tego ręcznie jest dla mnie niewygodne. Czy istnieje sposób, aby powiedzieć Roomowi: nie dotykam żadnej z istniejącej tabeli, więc dane są bezpieczne. Proszę utworzyć migrację dla mnie?

Piotr Aleksander Chmielowski
źródło
Czy znalazłeś rozwiązanie tego problemu?
Mikkel Larsen
3
Miałem ten sam problem i naprawiłem go w ten sam sposób, co ty, ale też nie znalazłem rozwiązania. Cieszę się, że nie jestem wtedy sam. :)
Mikkel Larsen
3
To samo tutaj. Uważam to za bardzo niewygodne, że pokój jest w stanie wygenerować zapytanie tworzenia wewnątrz bazy danych database_impl, ale nie może po prostu utworzyć tabeli po rozpoczęciu migracji ....
JacksOnF1re
1
Dałbym tyle za taką funkcję ... Fajnie byłoby też pomieszać migracje i mechanizm
awaryjny
3
Nie jestem pewien, czy to byłoby pomocne, ale Room ma możliwość wyeksportowania schematu bazy danych do pliku JSON. developer.android.com/training/data-storage/room/… Oczywiście nadal oznaczałoby to ręczne dodanie skryptu migracji, ale nie trzeba by było przechodzić przez automatycznie generowane klasy, aby uzyskać instrukcję SQL.
James Lendrem

Odpowiedzi:

83

Pokój ma nie mieć dobry system migracji, przynajmniej nie do czasu 2.1.0-alpha03.

Tak więc, dopóki nie będziemy mieć lepszego systemu migracji, istnieją pewne obejścia umożliwiające łatwe migracje w pokoju.

Ponieważ nie ma takiej metody jak @Database(createNewTables = true) lub MigrationSystem.createTable(User::class), która powinna być taka czy inna, jedyną możliwą drogą jest bieganie

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

wewnątrz twojej migratemetody.

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`))")
    }
}

Aby uzyskać powyższy skrypt SQL , masz 4 sposoby

1. Napisz sam

Zasadniczo musisz napisać powyższy skrypt, który będzie pasował do skryptu generowanego przez Room. Ten sposób jest możliwy, niewykonalny. (Weź pod uwagę, że masz 50 pól)

2. Schemat eksportu

Jeśli umieścisz exportSchema = truewewnątrz @Databaseadnotacji, Room wygeneruje schemat bazy danych w / schemas folderu projektu. Użycie jest

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

Upewnij się, że w build.grademodule aplikacji uwzględniono poniższe wiersze

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

Kiedy uruchomisz lub zbudujesz projekt, otrzymasz plik JSON 2.json, który zawiera wszystkie zapytania w Twojej bazie danych 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
          },

Możesz więc uwzględnić powyższe createSqlw swojej migratemetodzie.

3. Uzyskaj zapytanie z AppDatabase_Impl

Jeśli nie chcesz eksportować schematu, nadal możesz uzyskać zapytanie, uruchamiając lub budując projekt, który wygeneruje AppDatabase_Impl.javaplik. i w określonym pliku, który możesz mieć.

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

W ramach createAllTablesmetody zostaną utworzone skrypty wszystkich podmiotów. Możesz to zdobyć i uwzględnić w swojej migratemetodzie.

4. Przetwarzanie adnotacji.

Jak można się domyślać, Pokój generuje wszystkie wyżej wymienione schema, a AppDatabase_Implpliki w czasie kompilacji oraz z dopiskiem przetworzeniu, które dodasz z

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

Oznacza to, że możesz również zrobić to samo i stworzyć własną bibliotekę przetwarzania adnotacji, która generuje wszystkie niezbędne zapytania tworzenia.

Chodzi o to, aby utworzyć bibliotekę przetwarzania adnotacji dla adnotacji pokojów @Entityi @Database. Weźmy na przykład zajęcia oznaczone adnotacją @Entity. Oto kroki, które będziesz musiał wykonać

  1. Utwórz nowy StringBuilderi dołącz „UTWÓRZ TABELĘ, JEŚLI NIE ISTNIEJE”
  2. Uzyskaj nazwę tabeli z class.simplenamelub według tableNamepola @Entity. Dodaj to do swojegoStringBuilder
  3. Następnie dla każdego pola Twojej klasy utwórz kolumny SQL. Weź nazwę, typ, dopuszczalność wartości null pola według samego pola lub @ColumnInfoadnotacji. Dla każdego pola musisz dodać id INTEGER NOT NULLstyl kolumny do swojego StringBuilder.
  4. Dodaj klucze podstawowe według @PrimaryKey
  5. Dodaj ForeignKeyi Indicesjeśli istnieje.
  6. Po zakończeniu zamień go na łańcuch i zapisz w nowej klasie, której chcesz użyć. Na przykład zapisz to jak poniżej
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

Następnie możesz go użyć jako

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

Zrobiłem dla siebie taką bibliotekę, którą możesz sprawdzić, a nawet wykorzystać w swoim projekcie. Zwróć uwagę, że biblioteka, którą utworzyłem, nie jest pełna i po prostu spełnia moje wymagania dotyczące tworzenia tabel.

RoomExtension dla lepszej migracji

Aplikacja korzystająca z RoomExtension

Mam nadzieję, że to było przydatne.

AKTUALIZACJA

W chwili pisania tej odpowiedzi wersja pokoju była 2.1.0-alpha03i kiedy wysłałem e-mail do programistów, otrzymałem odpowiedź

Oczekuje się, że będzie miał lepszy system migracji w 2.2.0

Niestety wciąż brakuje nam lepszego systemu migracji.

musooff
źródło
3
Czy możesz wskazać, gdzie przeczytałeś, że Room 2.2.x będzie miał lepszą migrację? Nie mogę znaleźć niczego, co by to potwierdzało, a ponieważ obecnie pracujemy nad wersją beta 2.1.0, to, co jest w wersji 2.2.0, wydaje się w tej chwili nieznane.
jkane001
1
@ jkane001 Wysłałem e-mail do jednego z twórców pokoju i otrzymałem odpowiedź tak. Nie ma takiego publicznego ogłoszenia dotyczącego wersji 2.2.x (jeszcze?)
musooff
4
Obecnie w wersji 2.2.2 i nadal nie ma lepszej migracji :( Jednak jest to doskonała odpowiedź i zaoszczędziła mi mnóstwo pracy, więc +1 za to.
smitty1
@androiddeveloper Wszystko z wyjątkiem # 4, czyli przetwarzania adnotacji
musooff
1
@musooff Myślę, że dodanie tworzenia tabeli jest w porządku. To najbezpieczniejszy sposób na skopiowanie kodu z funkcji „createAllTables”.
programista Androida
4

Przepraszamy, Room nie obsługuje automatycznego tworzenia tabel bez utraty danych.

Zapis migracji jest obowiązkowy. W przeciwnym razie usunie wszystkie dane i utworzy nową strukturę tabeli.

Viswanath Kumar Sandu
źródło
0

Możesz to zrobić w ten sposób-

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

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

Pozostałe będą takie same, jak wspomniałeś powyżej-

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

Odniesienie - więcej

Sujeet Kumar
źródło
0

Możesz dodać następujące polecenie gradle do domyślnej konfiguracji w pliku app.gradle:

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

Po uruchomieniu skompiluje listę nazw tabel z odpowiednimi instrukcjami CREATE TABLE, z których można po prostu skopiować i wkleić do obiektów migracji. Może być konieczna zmiana nazw tabel.

Na przykład to jest z mojego wygenerowanego schematu:

"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`))"

I tak kopiuję, wklejam instrukcję createSql i zmieniam „$ {TABLE_NAME}” na „asset” nazwę tabeli i voila, automatycznie generowane instrukcje Room create.

Larry Stent
źródło
-1

W takim przypadku nie musisz wykonywać migracji, możesz wywołać .fallbackToDestructiveMigration () podczas tworzenia instancji bazy danych.

Przykład:

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

I nie zapomnij zmienić wersji bazy danych.

rudicjovan
źródło
To rozwiązanie usunie wszystkie moje dane z istniejących tabel.
Piotr Aleksander Chmielowski
Niestety tak. „Możesz wywołać tę metodę, aby zmienić to zachowanie i ponownie utworzyć bazę danych bez awarii. Należy pamiętać, że spowoduje to usunięcie wszystkich danych w tabelach bazy danych zarządzanych przez funkcję Room”.
rudicjovan
-2

Może w tym przypadku (jeśli utworzyłeś tylko nową tabelę bez zmiany innych) możesz to zrobić bez tworzenia żadnych migracji?

user1730694
źródło
1
Nie, w tym przypadku pokój wyrzuca logi: java.lang.IllegalStateException: Konieczna jest migracja z {old_version} do {new_version}
Max Makeichik