Raumdatenbankmigration, wenn nur eine neue Tabelle hinzugefügt wird

97

Nehmen wir nicht an, ich habe eine einfache Raumdatenbank:

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

Jetzt füge ich eine neue Entität hinzu: Petund stoße Version auf 2:

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

Natürlich wirft Room eine Ausnahme: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

Angenommen, ich habe die UserKlasse nicht geändert (damit alle Daten sicher sind), muss ich eine Migration bereitstellen, die nur eine neue Tabelle erstellt. Ich untersuche also Klassen, die von Room generiert wurden, suche nach generierten Abfragen, um meine neue Tabelle zu erstellen, kopiere sie und füge sie in die Migration ein:

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

Ich finde es jedoch unpraktisch, es manuell zu tun. Gibt es eine Möglichkeit, Room zu sagen: Ich berühre keine der vorhandenen Tabellen, daher sind Daten sicher. Bitte erstellen Sie eine Migration für mich?

Piotr Aleksander Chmielowski
quelle
Haben Sie eine Lösung dafür gefunden?
Mikkel Larsen
3
Ich hatte das gleiche Problem und habe es genauso behoben wie Sie und auch keine Lösung gefunden. Ich bin froh, dass ich dann nicht allein bin. :)
Mikkel Larsen
3
Hier gilt das gleiche. Ich finde es sehr unpraktisch, dass Raum die Erstellungsabfrage innerhalb der Datenbank_impl generieren kann, aber nicht einfach die Tabelle erstellen kann, sobald die Migration beginnt ....
JacksOnF1re
1
Ich würde so viel für eine solche Funktion geben ... Es wäre auch schön, Migrationen und den Fallback-Mechanismus zu mischen ...
Appyx
3
Ich bin nicht sicher, ob dies hilfreich wäre, aber Room hat die Option, das Datenbankschema in eine JSON-Datei zu exportieren. developer.android.com/training/data-storage/room/… Dies würde natürlich immer noch das manuelle Hinzufügen des Migrationsskripts bedeuten, aber Sie müssten nicht durch die automatisch generierten Klassen routen, um Ihre SQL-Anweisung zu erhalten.
James Lendrem

Antworten:

75

Der Raum hat KEIN gutes Migrationssystem, zumindest nicht bis 2.1.0-alpha03.

Bis wir ein besseres Migrationssystem haben, gibt es einige Problemumgehungen für einfache Migrationen im Raum.

Da es keine Methode wie @Database(createNewTables = true) oder gibt MigrationSystem.createTable(User::class), die es geben sollte, ist der einzig mögliche Weg das Ausführen

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

innerhalb Ihrer migrateMethode.

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

Um über das SQL- Skript hinauszukommen , haben Sie vier Möglichkeiten

1. Schreiben Sie selbst

Grundsätzlich müssen Sie das obige Skript schreiben, das mit dem von Room generierten Skript übereinstimmt. Dieser Weg ist möglich, nicht machbar. (Angenommen, Sie haben 50 Felder)

2. Schema exportieren

Wenn Sie exportSchema = truein Ihre @DatabaseAnmerkung aufnehmen, generiert Room ein Datenbankschema in / schemas Ihres Projektordners. Die Verwendung ist

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

Stellen Sie sicher, dass Sie die folgenden Zeilen in build.gradeIhr App-Modul aufgenommen haben

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

Wenn Sie das Projekt ausführen oder erstellen, erhalten Sie eine JSON-Datei 2.json, die alle Abfragen in Ihrer Room-Datenbank enthält.

  "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
          },

Sie können also das oben createSqlGenannte in Ihre migrateMethode aufnehmen.

3. Abfrage von AppDatabase_Impl abrufen

Wenn Sie kein Schema exportieren möchten, können Sie die Abfrage trotzdem abrufen, indem Sie das Projekt ausführen oder AppDatabase_Impl.javaerstellen, das eine Datei generiert . und innerhalb der angegebenen Datei, die Sie haben können.

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

Innerhalb der createAllTablesMethode gibt es die Erstellungsskripte aller Entitäten. Sie können es erhalten und in Ihre migrateMethode einbeziehen.

4. Anmerkungsverarbeitung.

Wie Sie sich vorstellen können, generiert Room alle oben genannten Dateien schemaund AppDatabase_ImplDateien innerhalb der Kompilierungszeit und mit der von Ihnen hinzugefügten Anmerkungsverarbeitung

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

Das heißt, Sie können auch das Gleiche tun und eine eigene Bibliothek zur Verarbeitung von Anmerkungen erstellen, die alle erforderlichen Erstellungsabfragen für Sie generiert.

Die Idee ist, eine Bibliothek zur Verarbeitung von Anmerkungen für Raumanmerkungen von @Entityund zu erstellen @Database. Nehmen Sie eine Klasse, die @Entityzum Beispiel mit Anmerkungen versehen ist . Dies sind die Schritte, denen Sie folgen müssen

  1. StringBuilderErstellen Sie eine neue und fügen Sie "TABELLE ERSTELLEN, WENN NICHT EXISTIERT" hinzu.
  2. Holen Sie sich den Tabellennamen entweder aus class.simplenameoder nach tableNameFeld von @Entity. Fügen Sie es Ihrem hinzuStringBuilder
  3. Erstellen Sie dann für jedes Feld Ihrer Klasse SQL-Spalten. Nehmen Sie den Namen, den Typ und die Nullfähigkeit des Feldes entweder durch das Feld selbst oder durch @ColumnInfoAnmerkungen. Für jedes Feld müssen Sie id INTEGER NOT NULLIhren Spaltenstil hinzufügen StringBuilder.
  4. Primärschlüssel hinzufügen durch @PrimaryKey
  5. Hinzufügen ForeignKeyund Indicesfalls vorhanden.
  6. Konvertieren Sie es nach Abschluss in einen String und speichern Sie es in einer neuen Klasse, die Sie verwenden möchten. Speichern Sie es beispielsweise wie unten beschrieben
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

Dann können Sie es als verwenden

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

Ich habe mir eine solche Bibliothek erstellt, die Sie auschecken und sogar in Ihrem Projekt verwenden können. Beachten Sie, dass die von mir erstellte Bibliothek nicht voll ist und nur meine Anforderungen für die Tabellenerstellung erfüllt.

RoomExtension für eine bessere Migration

Anwendung, die RoomExtension verwendet

Hoffe es war nützlich.

AKTUALISIEREN

Zum Zeitpunkt des Schreibens dieser Antwort war die 2.1.0-alpha03Raumversion und als ich Entwicklern eine E-Mail schickte, erhielt ich eine Antwort von

Es wird erwartet, dass es ein besseres Migrationssystem gibt 2.2.0

Leider fehlt uns immer noch ein besseres Migrationssystem.

musooff
quelle
3
Können Sie darauf hinweisen, wo Sie gelesen haben, dass Raum 2.2.x eine bessere Migration bietet? Ich kann nichts finden, was diese Behauptung aufstellt, und da wir derzeit an der Beta-Version 2.1.0 arbeiten, scheint das, was in 2.2.0 enthalten ist, derzeit unbekannt zu sein.
jkane001
1
@ jkane001 Ich habe einem der Raumentwickler eine E-Mail geschickt und eine Antwort erhalten. Es gibt keine solche öffentliche Bekanntmachung bezüglich 2.2.x (noch?)
musooff
4
Derzeit auf Version 2.2.2 und immer noch keine bessere Migration :( Dies ist jedoch eine ausgezeichnete Antwort und hat mir eine
Menge
@androiddeveloper Alle außer # 4, das ist Annotation Processing
musooff
1
@musooff Ich denke eigentlich, dass es in Ordnung ist, die Tabellenerstellung hinzuzufügen. Dies ist der sicherste Weg, um den Code aus der Funktion "createAllTables" zu kopieren.
Android-Entwickler
3

Leider unterstützt Room die automatische Erstellung von Tabellen ohne Datenverlust nicht.

Es ist obligatorisch, die Migration zu schreiben. Andernfalls werden alle Daten gelöscht und die neue Tabellenstruktur erstellt.

Viswanath Kumar Sandu
quelle
0

Sie können dies tun-

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

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

Der Rest ist derselbe wie oben erwähnt.

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

Referenz - Für mehr

Sujeet Kumar
quelle
0

Sie können den folgenden Gradle-Befehl zu Ihrer defaultConfig in Ihrer app.gradle hinzufügen:

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

Wenn Sie dies ausführen, wird eine Liste von Tabellennamen mit den entsprechenden CREATE TABLE-Anweisungen erstellt, aus denen Sie sie einfach kopieren und in Ihre Migrationsobjekte einfügen können. Möglicherweise müssen Sie die Tabellennamen ändern.

Dies stammt beispielsweise aus meinem generierten Schema:

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

Also kopiere ich, füge die createSql-Anweisung ein und ändere '$ {TABLE_NAME}' in 'Assets', den Tabellennamen, und voila automatisch generierte Room-Create-Anweisungen.

Larry Stent
quelle
-1

In diesem Fall müssen Sie keine Migration durchführen. Sie können .fallbackToDestructiveMigration () aufrufen, wenn Sie eine Datenbankinstanz erstellen.

Beispiel:

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

Und vergessen Sie nicht, die Datenbankversion zu ändern.

rudicjovan
quelle
Diese Lösung entfernt alle meine Daten aus vorhandenen Tabellen.
Piotr Aleksander Chmielowski
Leider ja. "Sie können diese Methode aufrufen, um dieses Verhalten zu ändern und die Datenbank neu zu erstellen, anstatt abzustürzen. Beachten Sie, dass dadurch alle Daten in den von Room verwalteten Datenbanktabellen gelöscht werden."
Rudicjovan
-2

Vielleicht können Sie in diesem Fall (wenn Sie nur eine neue Tabelle erstellt haben, ohne andere zu ändern) dies tun, ohne Migrationen zu erstellen?

user1730694
quelle
1
Nein, in diesem Fall wirft der Raum Protokolle ein: java.lang.IllegalStateException: Eine Migration von {old_version} nach {new_version} ist erforderlich
Max Makeichik