Migration de la base de données de salle si seule une nouvelle table est ajoutée


99

Ne supposons pas, j'ai une base de données de salle simple:

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

Maintenant, j'ajoute une nouvelle entité: Petet passe la version à 2:

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

Bien sûr, Room lève une exception: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

En supposant que je n'ai pas changé de Userclasse (donc toutes les données sont en sécurité), je dois fournir une migration qui crée simplement une nouvelle table. Donc, je regarde dans les classes générées par Room, je recherche la requête générée pour créer ma nouvelle table, je la copie et la colle dans la migration:

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

Cependant, je trouve peu pratique de le faire manuellement. Existe-t-il un moyen de dire à Room: Je ne touche à aucune table existante, donc les données sont en sécurité. Veuillez créer une migration pour moi?


Avez-vous trouvé une solution à cela?
Mikkel Larsen

3
J'ai eu le même problème et je l'ai résolu de la même manière que vous, et je n'ai pas non plus trouvé de solution. Content que je ne sois pas seul alors. :)
Mikkel Larsen

3
Pareil ici. Je trouve très gênant que room soit capable de générer la requête de création à l'intérieur de database_impl, mais ne peut pas simplement créer la table, une fois que la migration commence ...
JacksOnF1re

1
Je donnerais tellement pour une telle fonctionnalité ... Ce serait aussi bien de mélanger les migrations et le mécanisme de secours ...
Appyx

3
Je ne sais pas si cela serait utile, mais Room a la possibilité d'exporter le schéma de base de données dans un fichier JSON. developer.android.com/training/data-storage/room/ ... Évidemment, cela signifierait toujours l'ajout manuel du script de migration, mais vous n'auriez pas besoin de passer par les classes générées automatiquement pour obtenir votre instruction SQL.
James Lendrem

Réponses:


75

La salle n'a PAS un bon système de migration, du moins pas avant 2.1.0-alpha03.

Donc, jusqu'à ce que nous ayons un meilleur système de migration, il existe des solutions de contournement pour avoir des migrations faciles dans la salle.

Comme il n'y a pas de méthode telle que @Database(createNewTables = true) ou MigrationSystem.createTable(User::class), qu'il devrait y avoir l'une ou l'autre, la seule façon possible est d'exécuter

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

à l'intérieur de votre migrateméthode.

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

Afin d'obtenir au-dessus du script SQL , vous avez 4 façons

1. Écrivez par vous-même

Fondamentalement, vous devez écrire le script ci-dessus qui correspondra au script généré par Room. Cette manière est possible, pas faisable. (Considérez que vous avez 50 champs)

2. Exporter le schéma

Si vous incluez exportSchema = truedans votre @Databaseannotation, Room générera un schéma de base de données dans / schemas de votre dossier de projet. L'utilisation est

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

Assurez-vous que vous avez inclus les lignes ci-dessous dans build.gradevotre module d'application

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

Lorsque vous exécutez ou générez le projet, vous obtiendrez un fichier JSON contenant 2.jsontoutes les requêtes de votre base de données 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
          },

Ainsi, vous pouvez inclure ce qui précède createSqldans votre migrateméthode.

3. Obtenir une requête depuis AppDatabase_Impl

Si vous ne souhaitez pas exporter le schéma, vous pouvez toujours obtenir la requête en exécutant ou en créant le projet qui générera le AppDatabase_Impl.javafichier. et dans le fichier spécifié, vous pouvez avoir.

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

Dans la createAllTablesméthode, il y aura les scripts de création de toutes les entités. Vous pouvez l'obtenir et l'inclure dans votre migrateméthode.

4. Traitement des annotations.

Comme vous pouvez le deviner, Room génère tous les fichiers mentionnés ci-dessus schemaet les AppDatabase_Implfichiers dans le temps de compilation et avec le traitement d'annotation que vous ajoutez avec

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

Cela signifie que vous pouvez également faire de même et créer votre propre bibliothèque de traitement d'annotations qui génère toutes les requêtes de création nécessaires pour vous.

L'idée est de créer une bibliothèque de traitement des annotations pour les annotations de salle de @Entityet @Database. Prenons par exemple une classe annotée @Entity. Voici les étapes que vous devrez suivre

  1. Créez un nouveau StringBuilderet ajoutez "CREATE TABLE IF NOT EXISTS"
  2. Obtenez le nom de la table à partir class.simplenameou par tableNamechamp de @Entity. Ajoutez-le à votreStringBuilder
  3. Ensuite, pour chaque champ de votre classe, créez des colonnes de SQL. Prenez le nom, le type, la nullité du champ soit par le champ lui-même, soit par @ColumnInfoannotation. Pour chaque champ, vous devez ajouter le id INTEGER NOT NULLstyle d'une colonne à votre StringBuilder.
  4. Ajouter des clés primaires par @PrimaryKey
  5. Ajouter ForeignKeyet Indicess'il existe.
  6. Après avoir terminé, convertissez-le en chaîne et enregistrez-le dans une nouvelle classe que vous souhaitez utiliser. Par exemple, enregistrez-le comme ci-dessous
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

Ensuite, vous pouvez l'utiliser comme

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

J'ai créé une telle bibliothèque pour moi-même que vous pouvez consulter et même l'utiliser dans votre projet. Notez que la bibliothèque que j'ai créée n'est pas pleine et qu'elle répond simplement à mes exigences pour la création de table.

RoomExtension pour une meilleure migration

Application qui utilise RoomExtension

J'espère que c'était utile.

METTRE À JOUR

Au moment d'écrire cette réponse, la version de la salle était 2.1.0-alpha03et quand j'ai envoyé un e-mail aux développeurs, j'ai reçu une réponse de

Il est prévu d'avoir un meilleur système de migration dans 2.2.0

Malheureusement, il nous manque encore un meilleur système de migration.


3
Pouvez-vous indiquer où vous avez lu que Room 2.2.x aura une meilleure migration? Je ne trouve rien qui fasse cette affirmation, et puisque nous travaillons actuellement sur la version bêta de la version 2.1.0, ce qui se trouve dans la version 2.2.0 semble être inconnu pour le moment.
jkane001

1
@ jkane001 J'ai envoyé un e-mail à l'un des développeurs de la salle et j'ai obtenu une réponse. Il n'y a pas de tel avis public concernant 2.2.x (encore?)
musooff

4
Actuellement sur la version 2.2.2 et toujours pas de meilleure migration: (Cependant, c'est une excellente réponse et m'a sauvé une tonne de travail donc +1 pour cela.
smitty1

@androiddeveloper Tous sauf le # 4, qui est le traitement des annotations
musooff

1
@musooff Je pense en fait que c'est correct d'ajouter la création de table. C'est le moyen le plus sûr de copier le code de la fonction "createAllTables".
développeur android le

3

Désolé, Room ne prend pas en charge la création automatique de tables sans perte de données.

Il est obligatoire d'écrire la migration. Sinon, cela effacera toutes les données et créera la nouvelle structure de table.


0

Vous pouvez faire de cette façon-

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

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

Le reste sera le même que celui que vous avez mentionné ci-dessus-

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

Référence - Pour en savoir plus


0

Vous pouvez ajouter la commande gradle suivante à votre defaultConfig dans votre app.gradle:

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

Lorsque vous l'exécutez, il compilera une liste de noms de table avec leurs instructions CREATE TABLE pertinentes à partir de laquelle vous pourrez simplement copier et coller dans vos objets de migration. Vous devrez peut-être changer les noms de table.

Par exemple, cela provient de mon schéma généré:

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

Et donc, je copie, collez l'instruction createSql et changez le '$ {TABLE_NAME}' en 'assets' le nom de la table, et voilà les instructions de création de salle générées automatiquement.


-1

Dans ce cas, vous n'avez pas besoin d'effectuer une migration, vous pouvez appeler .fallbackToDestructiveMigration () lorsque vous créez une instance de base de données.

Exemple:

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

Et n'oubliez pas de changer la version de la base de données.


Cette solution supprimera toutes mes données des tables existantes.
Piotr Aleksander Chmielowski

Malheureusement oui. "Vous pouvez appeler cette méthode pour modifier ce comportement afin de recréer la base de données au lieu de planter. Notez que cela supprimera toutes les données des tables de base de données gérées par Room."
rudicjovan

-2

Peut-être que dans ce cas (si vous avez seulement créé une nouvelle table sans en changer d'autres), vous pouvez le faire sans créer de migrations du tout?


1
Non, dans ce cas, la salle jette les journaux: java.lang.IllegalStateException: Une migration de {old_version} vers {new_version} est nécessaire
Max Makeichik
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.