Android: Aktualisieren der DB-Version und Hinzufügen einer neuen Tabelle


117

Ich habe bereits SQLite-Tabellen für meine App erstellt, möchte aber jetzt der Datenbank eine neue Tabelle hinzufügen.

Ich habe die DB-Version wie folgt geändert

private static final int DATABASE_VERSION = 2;

und Zeichenfolge zum Erstellen einer Tabelle hinzugefügt

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

onCreateund onUpgradewie folgt:

@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);

    }

Aus irgendeinem Grund wird die neue Tabelle jedoch nicht erstellt. Was mache ich falsch?


Dies ist eine weitere interessante Lösung, aber die bisher robusteste Version, die ich je gesehen habe, ist hier .
Suragch

Antworten:


280

1. Über onCreate () und onUpgrade ()

onCreate(..)wird aufgerufen, wenn die App neu installiert wird. onUpgradewird aufgerufen, wenn die App aktualisiert und gestartet wird und die Datenbankversion nicht identisch ist.

2. Inkrementieren der Datenbankversion

Sie benötigen einen Konstruktor wie:

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

WICHTIG: Das Inkrementieren der App-Version allein reicht nicht aus onUpgrade, um aufgerufen zu werden!

3. Vergessen Sie nicht Ihre neuen Benutzer!

Vergiss nicht hinzuzufügen

database.execSQL(DATABASE_CREATE_color);

Auch bei Ihrer onCreate () -Methode fehlt bei neu installierten Apps die Tabelle.

4. Umgang mit mehreren Datenbankänderungen im Laufe der Zeit

Wenn Sie aufeinanderfolgende App-Upgrades haben, von denen einige Datenbank-Upgrades haben, sollten Sie Folgendes überprüfen 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); 
   }
}

Auf diese Weise erhält ein Benutzer beim Upgrade von Version 1 auf Version 3 beide Updates. Wenn ein Benutzer ein Upgrade von Version 2 auf 3 durchführt, erhält er nur das Update für Version 3 ... Schließlich können Sie sich nicht darauf verlassen, dass 100% Ihrer Benutzerbasis jedes Mal aktualisiert werden, wenn Sie ein Update veröffentlichen. Manchmal überspringen sie ein Update oder 12 :)

5. Behalten Sie Ihre Revisionsnummern während der Entwicklung unter Kontrolle

Und schließlich ... anrufen

adb uninstall <yourpackagename>

deinstalliert die App vollständig. Wenn Sie erneut installieren, werden Sie garantiert getroffen, onCreatewas Sie davon abhält, die Datenbankversion während der Entwicklung in die Stratosphäre zu erhöhen ...


5
Zu # 4: Wäre es nicht eine bessere Idee, das übergebene oldVersionArgument zu verwenden ? Wenn Upgrade-Anweisungen wiederholbar sind, werden Sie sie möglicherweise in einer meist aktuellen Datenbank wiederholen. Wenn eine der Anweisungen darin besteht, eine Tabelle abzuschneiden, wäre das sehr schlecht.
Greyson

3
@Greyson: Großartiger Punkt! Ehrlich gesagt fühle ich mich ein bisschen dumm, weil ich nie wirklich darüber nachgedacht habe. Manchmal denke ich, wir gewöhnen uns an, die gewünschten Argumente zu verwenden und den Rest zu ignorieren!
jkschneider

1
Sie steuern die Datenbank, warum sollten Sie den Namen ändern?
jkschneider

3
newVersionist irgendwie nutzlos, da Sie die aktuelle Datenbankversion ohnehin immer im Konstruktor festlegen (siehe Teil 2) und sie immer übereinstimmt. Die Schlüsselidee dabei ist, dass Sie nicht einfach von newVersionjedem Ort aus, an dem sich der Benutzer gerade befindet, ein Upgrade durchführen möchten, ohne jedes andere inkrementelle Upgrade dazwischen durchzuführen.
jkschneider

2
@kai Die CREATE_READINGSLogik sollte niemals in onUpgrade sein, da sie in der onCreateMethode Ihrer ersten Version enthalten war. Stellen Sie sich die Fälle im onUpgradeSwitch als "Ich aktualisiere FROM oldVersion" vor. Sie würden die Lesetabelle nicht erstellen, wenn Sie ein Upgrade von Version 1 durchführen würden, da diese bereits vorhanden sein sollte. Hoffentlich macht das Sinn ...
jkschneider

9

Ihr Code sieht korrekt aus. Mein Vorschlag ist, dass die Datenbank bereits denkt, dass sie aktualisiert wurde. Wenn Sie das Projekt nach dem Erhöhen der Versionsnummer, aber vor dem Hinzufügen des execSQLAufrufs ausgeführt haben, glaubt die Datenbank auf Ihrem Testgerät / Emulator möglicherweise bereits, dass es sich um Version 2 handelt.

Eine schnelle Möglichkeit, dies zu überprüfen, besteht darin, die Versionsnummer in 3 zu ändern. Wenn es danach aktualisiert wird, wissen Sie, dass dies nur darauf zurückzuführen ist, dass Ihr Gerät glaubte, dass es bereits aktualisiert wurde.


Dann war Ihr Code wie erwartet in Ordnung; nur nicht, wenn es inkrementell ausgeführt wurde. Denken Sie daran, die Tabellenerstellung so hinzuzufügen, onCreate()wie es jkschneider hervorhob.
Greyson

2

Sie können die SQLiteOpenHelper- onUpgradeMethode verwenden. In der onUpgrade-Methode erhalten Sie die oldVersion als einen der Parameter.

onUpgradeVerwenden Sie bei der Verwendung von a switchund in jedem der cases die Versionsnummer, um die aktuelle Version der Datenbank zu verfolgen.

Am besten wechseln Sie von oldVersionbis zu newVersion, erhöhen jeweils versionum 1 und aktualisieren dann die Datenbank Schritt für Schritt. Dies ist sehr hilfreich, wenn jemand mit Datenbankversion 1 die App nach langer Zeit auf eine Version mit Datenbankversion 7 aktualisiert und die App aufgrund bestimmter inkompatibler Änderungen abstürzt.

Anschließend werden die Aktualisierungen in der Datenbank schrittweise durchgeführt, wobei alle möglichen Fälle abgedeckt werden, dh die für jede neue Version vorgenommenen Änderungen in der Datenbank werden berücksichtigt, wodurch verhindert wird, dass Ihre Anwendung abstürzt.

Beispielsweise:

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;
    }

}

Wenn Sie diese break-Anweisungen entfernen, benötigen Sie keine Schleife
Tash Pemhiwa

Aber oldVersion muss in jedem Fall erhöht werden, um den nächsten Fall zu bestehen. @TashPemhiwa
Beulah Ana

Der Grund, warum eine switch-Anweisung eine Unterbrechung erfordert, ist, dass es möglich ist, mehrere Fälle gleichzeitig auszuführen - und dies ist auch dann der Fall, wenn die Fallbedingung nicht erfüllt ist. @BeulahAna
Tash Pemhiwa

Wenn Sie break hinzufügen und eine Datenbank eine alte oder aktuelle Version hat, kann Ihre Abfrage fehlschlagen, sodass break nicht erforderlich ist. Beispiel: Tabelle ändern Wenn eine Spalte bereits in einer Datenbankversion geändert wurde, ist Ihre Abfrage möglicherweise gemäß der Verlustsequenz der Datenbankversion
Neeraj

2

@ jkschneiders Antwort ist richtig. Es gibt jedoch einen besseren Ansatz.

Schreiben Sie die erforderlichen Änderungen für jedes Update in eine SQL-Datei, wie unter dem Link https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correctly/ beschrieben.

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) ;

Diese .sql-Dateien werden in der onUpgrade () -Methode entsprechend der Version der Datenbank ausgeführt.

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();
            }
        }
    }
}

Ein Beispielprojekt finden Sie auch unter demselben Link: https://github.com/riggaroo/AndroidDatabaseUpgrades


1
Ich wollte gerade hierher kommen und den gleichen Rat schreiben. Ich bin froh, dass du es schon getan hast. Die Leute sollten auf jeden Fall den Artikel lesen, auf den Sie verlinkt haben. Dies empfiehlt auch Android SQLiteAssetHelper für Upgrades. Es ist auch was CL. ( der SQLite-Experte hier für Stapelüberlauf) empfiehlt .
Suragch

Dieser Kommentar, wonach ich gesucht habe. Die SQL-Skripte, +1
Blueware

1

Der Umgang mit Datenbankversionen ist ein sehr wichtiger Bestandteil der Anwendungsentwicklung. Ich gehe davon aus, dass Sie bereits eine erweiterte AppDbHelper-Klasse haben SQLiteOpenHelper. Wenn Sie es erweitern, müssen Sie implementieren onCreateund onUpgrademethodisieren.

  1. Wann onCreateund onUpgradeMethoden aufgerufen

    • onCreate Wird aufgerufen, wenn die App neu installiert wird.
    • onUpgrade Wird aufgerufen, wenn die App aktualisiert wird.
  2. Organisieren von Datenbankversionen Ich verwalte Versionen in Klassenmethoden. Erstellen Sie die Implementierung der Schnittstellenmigration. ZB für die erste MigrationV1Klasse erstellen Klasse, zweite Version erstellen MigrationV1ToV2(dies sind meine Namenskonvention)


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

Beispielmigration:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Verwenden von Migrationsklassen

onCreate: Da onCreatewird aufgerufen, wenn die Anwendung neu installiert wird, müssen wir auch alle Migrationen ausführen (Aktualisierungen der Datenbankversion). So onCreatewird es aussehen:

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: Diese Methode wird aufgerufen, wenn die Anwendung bereits installiert und auf die neue Anwendungsversion aktualisiert ist. Wenn die Anwendung Datenbankänderungen enthält, fügen Sie alle Datenbankänderungen in die neue Migrationsklasse ein und erhöhen Sie die Datenbankversion.

Angenommen, der Benutzer hat eine Anwendung mit der Datenbankversion 1 installiert, und jetzt wird die Datenbankversion auf 2 aktualisiert (alle Schemaaktualisierungen bleiben erhalten MigrationV1ToV2). Wenn die Anwendung aktualisiert wird, müssen wir die Datenbank aktualisieren, indem wir Änderungen am Datenbankschema MigrationV1ToV2wie folgt anwenden :

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

Hinweis: Alle Upgrades (in erwähnt onUpgrade) des Datenbankschemas sollten in ausgeführt werdenonCreate

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.