Android: mise à niveau de la version DB et ajout d'une nouvelle table

118

J'ai déjà créé des tables sqlite pour mon application, mais je souhaite maintenant ajouter une nouvelle table à la base de données.

J'ai changé la version DB comme ci-dessous

private static final int DATABASE_VERSION = 2;

et Ajout d'une chaîne pour créer une table

private static final String DATABASE_CREATE_color = 
   "CREATE TABLE IF NOT EXISTS files(color text, incident_id text)";

onCreateet onUpgradecomme ci-dessous:

@Override
    public void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE_incident);
        database.execSQL(DATABASE_CREATE_audio);
        database.execSQL(DATABASE_CREATE_video);
        database.execSQL(DATABASE_CREATE_image);

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //drop table and add new tables when version 2 released.
        db.execSQL(DATABASE_CREATE_color);

    }

Mais pour une raison quelconque, la nouvelle table n'est pas créée. Qu'est-ce que je fais mal?

Jay Mayu
la source
C'est une autre solution intéressante, mais jusqu'à présent, la version la plus robuste que j'ai vue est ici .
Suragch le

Réponses:

280

1. À propos de onCreate () et onUpgrade ()

onCreate(..)est appelée chaque fois que l'application est fraîchement installée. onUpgradeest appelée chaque fois que l'application est mise à niveau et lancée et que la version de la base de données n'est pas la même.

2. Incrémentation de la version db

Vous avez besoin d'un constructeur comme:

MyOpenHelper(Context context) {
   super(context, "dbname", null, 2); // 2 is the database version
}

IMPORTANT: incrémenter la version de l'application seule ne suffit pas pour onUpgradeêtre appelé!

3. N'oubliez pas vos nouveaux utilisateurs!

N'oubliez pas d'ajouter

database.execSQL(DATABASE_CREATE_color);

à votre méthode onCreate () ou les applications nouvellement installées n'auront pas le tableau.

4. Comment gérer plusieurs changements de base de données au fil du temps

Lorsque vous avez des mises à niveau d'applications successives, dont plusieurs ont des mises à niveau de base de données, vous voulez être sûr de vérifier oldVersion:

onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   switch(oldVersion) {
   case 1:
       db.execSQL(DATABASE_CREATE_color);
       // we want both updates, so no break statement here...
   case 2:
       db.execSQL(DATABASE_CREATE_someothertable); 
   }
}

De cette façon, lorsqu'un utilisateur passe de la version 1 à la version 3, il obtient les deux mises à jour. Lorsqu'un utilisateur passe de la version 2 à la version 3, il ne reçoit que la mise à jour de la révision 3 ... Après tout, vous ne pouvez pas compter sur 100% de votre base d'utilisateurs pour mettre à jour chaque fois que vous publiez une mise à jour. Parfois, ils sautent une mise à jour ou 12 :)

5. Gardez vos numéros de révision sous contrôle tout en développant

Et enfin ... appeler

adb uninstall <yourpackagename>

désinstalle totalement l'application. Lorsque vous réinstallez, vous êtes assuré de frapper, onCreatece qui vous évite d'avoir à continuer à incrémenter la version de la base de données dans la stratosphère au fur et à mesure que vous développez ...

jkschneider
la source
5
Concernant le n ° 4: Ne serait-il pas préférable d'utiliser l' oldVersionargument adopté? Si des instructions de mise à niveau sont répétables, vous pouvez finir par les répéter sur une base de données essentiellement à jour. Si l'une des instructions est de tronquer une table, ce serait très mauvais.
Greyson
3
@Greyson: Excellent point! Honnêtement, je me sens un peu stupide de ne jamais vraiment y penser. Parfois, je pense que nous prenons l'habitude d'utiliser les arguments que nous voulons et d'ignorer le reste!
jkschneider
1
Vous contrôlez la base de données, pourquoi changeriez-vous le nom?
jkschneider
3
newVersionest un peu inutile, car vous définissez toujours la version actuelle de la base de données dans le constructeur (voir la partie 2) et elle correspondra toujours. L'idée clé ici est que vous ne voulez pas simplement effectuer une mise à niveau depuis l'endroit où l'utilisateur se trouve newVersionsans passer par toutes les autres mises à niveau incrémentielles entre les deux.
jkschneider
2
@kai La CREATE_READINGSlogique ne devrait jamais être dans onUpgrade, puisqu'elle était dans la onCreateméthode de votre première version. Considérez les cas dans le onUpgradecommutateur comme «Je mets à jour DE oldVersion». Vous ne créeriez pas le tableau des lectures si vous mettiez à niveau à partir de la version 1, car il devrait déjà exister. J'espère que cela a du sens ...
jkschneider
9

Votre code semble correct. Ma suggestion est que la base de données pense déjà qu'elle a été mise à niveau. Si vous avez exécuté le projet après avoir incrémenté le numéro de version, mais avant d'ajouter l' execSQLappel, la base de données de votre périphérique de test / émulateur peut déjà croire qu'elle est à la version 2.

Un moyen rapide de vérifier cela serait de changer le numéro de version en 3 - s'il est mis à niveau après cela, vous savez que c'est simplement parce que votre appareil pensait qu'il était déjà mis à niveau.

Greyson
la source
Ensuite, comme prévu, votre code était correct; mais pas quand il a été exécuté de manière incrémentielle. N'oubliez pas d'ajouter la création de table onCreate()comme l'a souligné jkschneider.
Greyson
2

Vous pouvez utiliser la onUpgrademéthode de SQLiteOpenHelper . Dans la méthode onUpgrade, vous obtenez l'ancienne version comme l'un des paramètres.

Dans l' onUpgradeutilisation a switchet dans chacun des cases, utilisez le numéro de version pour garder une trace de la version actuelle de la base de données.

Il est préférable de faire une boucle de oldVersionà newVersion, en incrémentant versionde 1 à la fois, puis de mettre à niveau la base de données étape par étape. Ceci est très utile lorsque quelqu'un avec la version 1 de la base de données met à niveau l'application après une longue période, vers une version utilisant la version 7 de la base de données et que l'application commence à planter en raison de certaines modifications incompatibles.

Ensuite, les mises à jour dans la base de données se feront par étapes, couvrant tous les cas possibles, c'est-à-dire en intégrant les modifications de la base de données effectuées pour chaque nouvelle version et en évitant ainsi que votre application ne plante.

Par exemple:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    switch (oldVersion) {
    case 1:
        String sql = "ALTER TABLE " + TABLE_SECRET + " ADD COLUMN " + "name_of_column_to_be_added" + " INTEGER";
        db.execSQL(sql);
        break;

    case 2:
        String sql = "SOME_QUERY";
        db.execSQL(sql);
        break;
    }

}
Vijesh Jat
la source
Si vous supprimez ces instructions break, vous n'aurez pas besoin d'une boucle
Tash Pemhiwa
mais oldVersion doit être incrémenté dans chaque cas pour passer le cas suivant @TashPemhiwa
Beulah Ana
La raison pour laquelle une instruction switch nécessite une pause est qu'il est possible d'exécuter plusieurs cas à la fois - et ce sera le cas même si la condition de cas n'est pas remplie, @BeulahAna
Tash Pemhiwa
Si vous ajoutez break et que certaines bases de données ont une version ancienne ou récente, votre requête peut échouer, donc break n'est pas nécessaire.Exemple de modification de table si une colonne est déjà modifiée dans une version de base de données, votre requête peut échouer selon la séquence de perte de la version de la base de données
Neeraj Singh
2

La réponse de @ jkschneider est juste. Cependant, il existe une meilleure approche.

Écrivez les modifications nécessaires dans un fichier sql pour chaque mise à jour comme décrit dans le lien https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correctly/

from_1_to_2.sql

ALTER TABLE books ADD COLUMN book_rating INTEGER;

from_2_to_3.sql

ALTER TABLE books RENAME TO book_information;

from_3_to_4.sql

ALTER TABLE book_information ADD COLUMN calculated_pages_times_rating INTEGER;
UPDATE book_information SET calculated_pages_times_rating = (book_pages * book_rating) ;

Ces fichiers .sql seront exécutés dans la méthode onUpgrade () selon la version de la base de données.

DatabaseHelper.java

public class DatabaseHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 4;

    private static final String DATABASE_NAME = "database.db";
    private static final String TAG = DatabaseHelper.class.getName();

    private static DatabaseHelper mInstance = null;
    private final Context context;

    private DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public static synchronized DatabaseHelper getInstance(Context ctx) {
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(BookEntry.SQL_CREATE_BOOK_ENTRY_TABLE);
        // The rest of your create scripts go here.

    }


    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.e(TAG, "Updating table from " + oldVersion + " to " + newVersion);
        // You will not need to modify this unless you need to do some android specific things.
        // When upgrading the database, all you need to do is add a file to the assets folder and name it:
        // from_1_to_2.sql with the version that you are upgrading to as the last version.
        try {
            for (int i = oldVersion; i < newVersion; ++i) {
                String migrationName = String.format("from_%d_to_%d.sql", i, (i + 1));
                Log.d(TAG, "Looking for migration file: " + migrationName);
                readAndExecuteSQLScript(db, context, migrationName);
            }
        } catch (Exception exception) {
            Log.e(TAG, "Exception running upgrade script:", exception);
        }

    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    private void readAndExecuteSQLScript(SQLiteDatabase db, Context ctx, String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            Log.d(TAG, "SQL script file name is empty");
            return;
        }

        Log.d(TAG, "Script found. Executing...");
        AssetManager assetManager = ctx.getAssets();
        BufferedReader reader = null;

        try {
            InputStream is = assetManager.open(fileName);
            InputStreamReader isr = new InputStreamReader(is);
            reader = new BufferedReader(isr);
            executeSQLScript(db, reader);
        } catch (IOException e) {
            Log.e(TAG, "IOException:", e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException:", e);
                }
            }
        }

    }

    private void executeSQLScript(SQLiteDatabase db, BufferedReader reader) throws IOException {
        String line;
        StringBuilder statement = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            statement.append(line);
            statement.append("\n");
            if (line.endsWith(";")) {
                db.execSQL(statement.toString());
                statement = new StringBuilder();
            }
        }
    }
}

Un exemple de projet est également fourni dans le même lien: https://github.com/riggaroo/AndroidDatabaseUpgrades

oiyio
la source
1
J'étais sur le point de venir ici et d'écrire le même conseil. Je suis content que vous l'ayez déjà fait. Les gens devraient absolument lire l'article auquel vous avez lié. C'est également ce que recommande Android SQLiteAssetHelper pour les mises à niveau. C'est aussi ce que CL. ( l' expert SQLite ici sur Stack Overflow) recommande .
Suragch le
Ce commentaire ce que je cherchais. Les scripts sql, +1
blueware
1

La gestion des versions de bases de données est une partie très importante du développement d'applications. Je suppose que vous avez déjà l'extension de la classe AppDbHelper SQLiteOpenHelper. Lorsque vous l'étendez, vous devrez l'implémenter onCreateet la onUpgrademéthode.

  1. Quand onCreateet onUpgrademéthodes appelées

    • onCreate appelé lorsque l'application est nouvellement installée.
    • onUpgrade appelé lors de la mise à jour de l'application.
  2. Organisation des versions de la base de données Je gère les versions dans une méthode de classe. Créer la mise en œuvre de la migration d'interface. Par exemple, pour la première version de la MigrationV1classe de création , la deuxième version de création MigrationV1ToV2(ce sont ma convention de dénomination)


    public interface Migration {
        void run(SQLiteDatabase db);//create tables, alter tables
    }

Exemple de migration:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Utilisation des classes de migration

onCreate: Étant donné onCreatequ'il sera appelé lorsque l'application fraîchement installée, nous devons également exécuter toutes les migrations (mises à jour de la version de la base de données). Alors onCreatevolonté se présente comme suit:

public void onCreate(SQLiteDatabase db){
        Migration mV1=new MigrationV1();
       //put your first database schema in this class
        mV1.run(db);
        Migration mV1ToV2=new MigrationV1ToV2();
        mV1ToV2.run(db);
        //other migration if any
  }

onUpgrade: Cette méthode sera appelée lorsque l'application est déjà installée et qu'elle est mise à jour vers la nouvelle version de l'application. Si l'application contient des modifications de base de données, placez toutes les modifications de base de données dans la nouvelle classe de migration et incrémentez la version de la base de données.

Par exemple, disons que l'utilisateur a installé une application qui a la version de base de données 1, et maintenant la version de la base de données est mise à jour à 2 (toutes les mises à jour de schéma sont conservées MigrationV1ToV2). Maintenant, lorsque l'application est mise à niveau, nous devons mettre à niveau la base de données en appliquant les changements de schéma de base de données MigrationV1ToV2comme ceci:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion < 2) {
        //means old version is 1
        Migration migration = new MigrationV1ToV2();
        migration.run(db);
    }
    if (oldVersion < 3) {
        //means old version is 2
    }
}

Remarque: toutes les mises à niveau (mentionnées dans onUpgrade) dans le schéma de base de données doivent être exécutées dansonCreate

VIjay J
la source