Android: aktualizacja wersji DB i dodanie nowej tabeli

117

Utworzyłem już tabele sqlite dla mojej aplikacji, ale teraz chcę dodać nową tabelę do bazy danych.

Zmieniłem wersję DB jak poniżej

private static final int DATABASE_VERSION = 2;

i Dodano ciąg do utworzenia tabeli

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

onCreatei onUpgradejak poniżej:

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

    }

Ale z jakiegoś powodu nowa tabela nie jest tworzona. Co ja robię źle?

Jay Mayu
źródło
To kolejne interesujące rozwiązanie, ale jak dotąd najbardziej solidna wersja, jaką widziałem, znajduje się tutaj .
Suragch

Odpowiedzi:

280

1. Informacje o onCreate () i onUpgrade ()

onCreate(..)jest wywoływana za każdym razem, gdy aplikacja jest świeżo zainstalowana. onUpgradejest wywoływana za każdym razem, gdy aplikacja jest aktualizowana i uruchamiana, a wersja bazy danych nie jest taka sama.

2. Zwiększanie wersji db

Potrzebujesz konstruktora takiego jak:

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

WAŻNE: Samo zwiększenie wersji aplikacji nie wystarczy, onUpgradeaby zadzwonić!

3. Nie zapomnij o nowych użytkownikach!

Nie zapomnij dodać

database.execSQL(DATABASE_CREATE_color);

do metody onCreate () lub w przypadku nowo zainstalowanych aplikacji będzie brakować tabeli.

4. Jak radzić sobie z wieloma zmianami bazy danych w czasie

W przypadku kolejnych uaktualnień aplikacji, z których kilka obejmuje uaktualnienia bazy danych, należy sprawdzić 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); 
   }
}

W ten sposób, gdy użytkownik zaktualizuje wersję 1 do wersji 3, otrzyma obie aktualizacje. Kiedy użytkownik uaktualnia z wersji 2 do 3, po prostu otrzymuje aktualizację do wersji 3 ... W końcu nie możesz liczyć na to, że 100% bazy użytkowników dokona aktualizacji za każdym razem, gdy wydasz aktualizację. Czasami pomijają aktualizację lub 12 :)

5. Utrzymywanie pod kontrolą numerów wersji podczas programowania

I wreszcie… dzwoni

adb uninstall <yourpackagename>

całkowicie odinstalowuje aplikację. Po ponownej instalacji masz gwarancję trafienia, onCreatedzięki czemu nie będziesz musiał zwiększać wersji bazy danych do stratosfery podczas opracowywania ...

jkschneider
źródło
5
Odnośnie # 4: Czy nie byłoby lepszym pomysłem użycie oldVersionprzekazanego argumentu? Jeśli jakiekolwiek instrukcje dotyczące aktualizacji są powtarzalne, możesz skończyć powtarzając je w większości aktualnej bazy danych. Jeśli jedno z instrukcji ma na celu obcięcie tabeli, byłoby to bardzo złe.
Greyson
3
@Greyson: Świetna uwaga! Szczerze mówiąc, czuję się trochę głupio, że nigdy o tym nie myślałem. Czasami wydaje mi się, że mamy w zwyczaju używać argumentów, które chcemy, i ignorować resztę!
jkschneider
1
Kontrolujesz bazę danych, dlaczego miałbyś zmieniać nazwę?
jkschneider
3
newVersionjest trochę bezużyteczna, ponieważ i tak zawsze ustawiasz aktualną wersję bazy danych w konstruktorze (patrz część 2) i zawsze będzie pasować. Kluczową ideą jest to, że nie chcesz po prostu aktualizować z miejsca, w którym użytkownik jest prosto, newVersionbez przechodzenia przez wszystkie inne przyrostowe aktualizacje pomiędzy.
jkschneider
2
@kai CREATE_READINGSLogika nigdy nie powinna znajdować się w onUpgrade, ponieważ była w onCreatemetodzie pierwszej wersji. Pomyśl o przypadkach w onUpgradeprzełączniku jako „Uaktualniam OD oldVersion”. Nie utworzyłbyś tabeli odczytów, gdybyś przeprowadzał aktualizację z wersji 1, ponieważ powinna już istnieć. Miejmy nadzieję, że to ma sens ...
jkschneider
9

Twój kod wygląda na prawidłowy. Moja sugestia jest taka, że ​​baza danych już myśli, że jest zaktualizowana. Jeśli wykonałeś projekt po zwiększeniu numeru wersji, ale przed dodaniem execSQLwywołania, baza danych na urządzeniu testowym / emulatorze może już sądzić, że jest w wersji 2.

Szybkim sposobem na sprawdzenie tego byłaby zmiana numeru wersji na 3 - jeśli później się zaktualizuje, wiesz, że to tylko dlatego, że Twoje urządzenie wierzyło, że zostało już zaktualizowane.

Greyson
źródło
Następnie, zgodnie z oczekiwaniami, kod był w porządku; tylko nie wtedy, gdy był uruchamiany przyrostowo. Pamiętaj, aby dodać tworzenie tabeli, onCreate()jak wskazał jkschneider.
Greyson
2

Możesz użyć onUpgrademetody SQLiteOpenHelper . W metodzie onUpgrade jako jeden z parametrów otrzymujesz oldVersion.

W przypadku onUpgradeużywania switchai w każdym z cases użyj numeru wersji, aby śledzić aktualną wersję bazy danych.

Najlepiej jest oldVersionwykonać pętlę od do newVersion, zwiększając versionją o 1, a następnie aktualizując bazę danych krok po kroku. Jest to bardzo pomocne, gdy ktoś z bazą danych w wersji 1 uaktualnia aplikację po długim czasie do wersji korzystającej z bazy danych w wersji 7, a aplikacja zaczyna się zawieszać z powodu pewnych niezgodnych zmian.

Następnie aktualizacje w bazie danych będą wykonywane krok po kroku, obejmując wszystkie możliwe przypadki, tj. Wprowadzając zmiany w bazie danych wykonane dla każdej nowej wersji i tym samym zapobiegając awariom aplikacji.

Na przykład:

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
źródło
Jeśli usuniesz te instrukcje przerwania, nie będziesz potrzebować pętli
Tash Pemhiwa
ale oldVersion musi zwiększać wartość w każdym przypadku, aby przejść do następnego przypadku @TashPemhiwa
Beulah Ana
Powodem, dla którego instrukcja przełącznika wymaga przerwy, jest to, że można uruchomić wiele spraw naraz - i tak będzie, nawet jeśli warunek przypadku nie zostanie spełniony, @BeulahAna
Tash Pemhiwa
Jeśli dodasz przerwę, a niektóre bazy danych mają starą lub najnowszą wersję, zapytanie może się nie powieść, więc przerwa nie jest wymagana Przykładowa zmiana tabeli, jeśli jakaś kolumna została już zmieniona w jakiejś wersji bazy danych, wówczas zapytanie może zakończyć się niepowodzeniem zgodnie z sekwencją utraty wersji
bazy danych
2

Odpowiedź @ jkschneider jest prawidłowa. Jest jednak lepsze podejście.

Zapisz potrzebne zmiany w pliku sql dla każdej aktualizacji, jak opisano w linku 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) ;

Te pliki .sql zostaną wykonane metodą onUpgrade () zgodnie z wersją bazy danych.

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

Przykładowy projekt znajduje się w tym samym linku również: https://github.com/riggaroo/AndroidDatabaseUpgrades

oiyio
źródło
1
Właśnie miałem tu przyjechać i napisać tę samą radę. Cieszę się, że już to zrobiłeś. Ludzie zdecydowanie powinni przeczytać artykuł, do którego masz link. Właśnie to zaleca Android SQLiteAssetHelper do aktualizacji. To także to, co CL. ( SQLite ekspert tutaj na przepełnienie stosu) zaleca .
Suragch
Ten komentarz to, czego szukałem. Skrypty sql, +1
blueware
1

Obsługa wersji baz danych jest bardzo ważną częścią tworzenia aplikacji. Zakładam, że masz już rozszerzającą klasę AppDbHelper SQLiteOpenHelper. Kiedy go rozszerzysz, będziesz musiał wdrożyć onCreatei onUpgrademetody.

  1. Kiedy onCreatei onUpgrademetody wywołane

    • onCreate wywoływana, gdy aplikacja jest nowo zainstalowana.
    • onUpgrade wywoływana po aktualizacji aplikacji.
  2. Organizowanie wersji baz danych Zarządzam wersjami w metodach klasowych. Stwórz implementację interfejsu Migration. Np. Dla pierwszej wersji create MigrationV1class, druga wersja create MigrationV1ToV2(taka jest moja konwencja nazewnictwa)


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

Przykładowa migracja:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Korzystanie z klas migracji

onCreate: Ponieważ onCreatezostanie wywołany, gdy aplikacja zostanie świeżo zainstalowana, musimy również wykonać wszystkie migracje (aktualizacje wersji bazy danych). Więc onCreatebędzie wyglądać tak:

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: Ta metoda zostanie wywołana, gdy aplikacja zostanie już zainstalowana i zostanie zaktualizowana do nowej wersji aplikacji. Jeśli aplikacja zawiera jakiekolwiek zmiany w bazie danych, umieść wszystkie zmiany w nowej klasie migracji i zwiększonej wersji bazy danych.

Na przykład, powiedzmy, że użytkownik zainstalował aplikację, która ma wersję bazy danych 1, a teraz wersja bazy danych jest aktualizowana do 2 (wszystkie aktualizacje schematu są zachowane MigrationV1ToV2). Teraz, gdy aplikacja jest aktualizowana, musimy zaktualizować bazę danych, stosując zmiany schematu bazy danych w MigrationV1ToV2następujący sposób:

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

Uwaga: Wszystkie aktualizacje (wymienione w onUpgrade) w schemacie bazy danych powinny być wykonywane w formacieonCreate

VIjay J
źródło