Migrazione del database della sala se viene aggiunta solo una nuova tabella


99

Non presumere, ho un semplice database di stanze:

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

Ora sto aggiungendo una nuova entità: Pete aggiungendo la versione a 2:

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

Ovviamente, Room genera un'eccezione: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

Supponendo che non ho cambiato Userclasse (quindi tutti i dati sono al sicuro), devo fornire la migrazione che crea solo una nuova tabella. Quindi, sto esaminando le classi generate da Room, cercando la query generata per creare la mia nuova tabella, copiandola e incollandola nella migrazione:

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

Tuttavia trovo scomodo farlo manualmente. C'è un modo per dire a Room: non sto toccando nessuna tabella esistente, quindi i dati sono al sicuro. Si prega di creare la migrazione per me?


Hai trovato una soluzione a questo?
Mikkel Larsen

3
Ho avuto lo stesso problema e l'ho risolto nello stesso modo in cui hai fatto tu, e non ho trovato una soluzione neanche. Sono contento di non essere solo allora. :)
Mikkel Larsen

3
Anch'io. Trovo molto scomodo che la stanza sia in grado di generare la query di creazione all'interno di database_impl, ma non può semplicemente creare la tabella, una volta avviata la migrazione ....
JacksOnF1re

1
Darei

3
Non sono sicuro che ciò sarebbe utile, ma Room ha la possibilità di esportare lo schema del database in un file JSON. developer.android.com/training/data-storage/room/… Ovviamente ciò significherebbe comunque aggiungere manualmente lo script di migrazione ma non sarà necessario instradare attraverso le classi generate automaticamente per ottenere l'istruzione SQL.
James Lendrem

Risposte:


76

Camera non non hanno una buona migrazione del sistema, almeno non fino a quando 2.1.0-alpha03.

Quindi, finché non avremo un sistema di migrazione migliore, ci sono alcune soluzioni alternative per avere migrazioni facili nella stanza.

Poiché non esiste un metodo come @Database(createNewTables = true) o MigrationSystem.createTable(User::class), che dovrebbe esserci l'uno o l'altro, l'unico modo possibile è correre

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

all'interno del tuo migratemetodo.

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

Per ottenere lo script SQL superiore , hai 4 modi

1. Scrivi da solo

Fondamentalmente, devi scrivere lo script sopra che corrisponderà allo script generato da Room. In questo modo è possibile, non fattibile. (Considera di avere 50 campi)

2. Esporta schema

Se includi exportSchema = trueall'interno @Databasedell'annotazione, Room genererà lo schema del database all'interno di / schemas della cartella del tuo progetto. L'utilizzo è

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

Assicurati di aver incluso le seguenti righe nel build.grademodulo dell'app

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

Quando esegui o crei il progetto otterrai un file JSON 2.json, che contiene tutte le query all'interno del tuo database 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
          },

Quindi, puoi includere quanto sopra createSqldentro di temigrate metodo.

3. Ottieni query da AppDatabase_Impl

Se non vuoi esportare lo schema puoi comunque ottenere la query eseguendo o costruendo il progetto che genererà il AppDatabase_Impl.javafile. e all'interno del file specificato puoi avere.

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

All'interno del createAllTablesmetodo, ci saranno gli script di creazione di tutte le entità. Puoi ottenerlo e includerlo dentro di temigrate metodo.

4. Elaborazione delle annotazioni.

Come puoi immaginare, Room genera tutto quanto sopra menzionato schemae AppDatabase_Implfile entro il tempo di compilazione e con Elaborazione annotazioni che aggiungi con

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

Ciò significa che puoi anche fare lo stesso e creare la tua libreria di elaborazione delle annotazioni che genera tutte le query di creazione necessarie per te.

L'idea è di creare una libreria di elaborazione delle annotazioni per le annotazioni Room di @Entitye @Database. Prendi una classe che è annotata con @Entityper esempio. Questi sono i passaggi che dovrai seguire

  1. Creane uno nuovo StringBuilder e aggiungi "CREA TABELLA SE NON ESISTE"
  2. Ottieni il nome della tabella da class.simplenameo per tableNamecampo di @Entity. Aggiungilo al tuoStringBuilder
  3. Quindi per ogni campo della tua classe crea colonne di SQL. Prendi il nome, il tipo, il nullability del campo dal campo stesso o @ColumnInfodall'annotazione. Per ogni campo, devi aggiungere lo id INTEGER NOT NULLstile di una colonna al tuo fileStringBuilder .
  4. Aggiungi chiavi primarie di @PrimaryKey
  5. Aggiungi ForeignKeyeIndices se esiste.
  6. Dopo aver finito convertilo in stringa e salvalo in una nuova classe che desideri utilizzare. Ad esempio, salvalo come di seguito
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

Quindi puoi usarlo come

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

Ho creato una libreria del genere per me stesso che puoi controllare e persino usarla nel tuo progetto. Nota che la libreria che ho creato non è piena e soddisfa solo i miei requisiti per la creazione di tabelle.

RoomExtension per una migliore migrazione

Applicazione che utilizza RoomExtension

Spero sia stato utile.

AGGIORNARE

Al momento della stesura di questa risposta, la versione room era 2.1.0-alpha03e quando ho inviato un'email agli sviluppatori ho ricevuto una risposta

Si prevede di avere un sistema di migrazione migliore in 2.2.0

Purtroppo, ci manca ancora un sistema di migrazione migliore.


3
Puoi indicare dove hai letto che Room 2.2.x avrà una migrazione migliore? Non riesco a trovare nulla che faccia questa affermazione, e dal momento che stiamo attualmente lavorando alla versione 2.1.0 beta, ciò che è nella 2.2.0 sembra essere sconosciuto al momento.
jkane001

1
@ jkane001 Ho inviato un'e-mail a uno degli sviluppatori della sala e ho ricevuto una risposta. Non esiste un avviso pubblico per quanto riguarda 2.2.x (ancora?)
musooff

4
Attualmente sulla versione 2.2.2 e ancora nessuna migrazione migliore :( Tuttavia, questa è una risposta eccellente e mi ha fatto risparmiare un sacco di lavoro quindi +1 per quello.
smitty1

@androiddeveloper Tutti tranne # 4, che è l'elaborazione delle annotazioni
musooff

1
@musooff In realtà penso che sia giusto aggiungere la creazione della tabella. È il modo più sicuro per copiare il codice dalla funzione "createAllTables".
sviluppatore Android

3

Spiacenti, Room non supporta la creazione automatica di tabelle senza perdita di dati.

È obbligatorio scrivere la migrazione. Altrimenti, cancellerà tutti i dati e creerà la nuova struttura della tabella.


0

Puoi farlo in questo modo-

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

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

Rimanere sarà lo stesso che hai menzionato sopra-

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

Riferimento - Per ulteriori informazioni


0

Puoi aggiungere il seguente comando gradle al tuo defaultConfig nel tuo app.gradle:

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

Quando lo esegui, compilerà un elenco di nomi di tabella con le relative istruzioni CREATE TABLE da cui puoi semplicemente copiare e incollare negli oggetti di migrazione. Potrebbe essere necessario modificare i nomi delle tabelle.

Ad esempio, questo è dal mio schema generato:

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

Quindi copio e incollo l'istruzione createSql e cambio "$ {TABLE_NAME}" in "assets" il nome della tabella, e voilà le istruzioni di creazione di Room generate automaticamente.


-1

In questo caso, non è necessario eseguire una migrazione, è possibile chiamare .fallbackToDestructiveMigration () quando si crea l'istanza del database.

Esempio:

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

E non dimenticare di cambiare la versione del database.


Questa soluzione rimuoverà tutti i miei dati dalle tabelle esistenti.
Piotr Aleksander Chmielowski

Sfortunatamente sì. "Puoi chiamare questo metodo per modificare questo comportamento per ricreare il database invece di bloccarsi. Tieni presente che questo cancellerà tutti i dati nelle tabelle del database gestite da Room."
rudicjovan

-2

Forse in questo caso (se hai solo creato una nuova tabella senza modificarne altre) puoi farlo senza creare alcuna migrazione?


1
No, in questo caso la stanza genera i log: java.lang.IllegalStateException: è necessaria una migrazione da {old_version} a {new_version}
Max Makeichik
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.