Android: aggiornamento della versione del DB e aggiunta di una nuova tabella


117

Ho già creato tabelle sqlite per la mia app, ma ora voglio aggiungere una nuova tabella al database.

Ho cambiato la versione del DB come sotto

private static final int DATABASE_VERSION = 2;

e Aggiunta una stringa per creare la tabella

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

onCreatee onUpgradecome di seguito:

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

    }

Ma per qualche motivo la nuova tabella non viene creata. Che cosa sto facendo di sbagliato?


Questa è un'altra soluzione interessante, ma finora la versione più robusta che ho visto è qui .
Suragch,

Risposte:


280

1. Informazioni su onCreate () e onUpgrade ()

onCreate(..)viene chiamato ogni volta che l'app viene installata di recente. onUpgradeviene chiamato ogni volta che l'app viene aggiornata e avviata e la versione del database non è la stessa.

2. Incremento della versione db

Hai bisogno di un costruttore come:

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

IMPORTANTE: l'incremento della versione dell'app da solo non è sufficiente per onUpgradeessere chiamato!

3. Non dimenticare i tuoi nuovi utenti!

Non dimenticare di aggiungere

database.execSQL(DATABASE_CREATE_color);

al tuo metodo onCreate () o alle app appena installate mancherà la tabella.

4. Come gestire più modifiche al database nel tempo

Quando si dispone di aggiornamenti successivi delle app, molti dei quali hanno aggiornamenti del database, è necessario assicurarsi di controllare 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); 
   }
}

In questo modo, quando un utente esegue l'aggiornamento dalla versione 1 alla versione 3, ottiene entrambi gli aggiornamenti. Quando un utente esegue l'aggiornamento dalla versione 2 alla 3, ottiene solo l'aggiornamento alla revisione 3 ... Dopo tutto, non puoi contare sul 100% della tua base utenti per eseguire l'aggiornamento ogni volta che rilasci un aggiornamento. A volte saltano un aggiornamento o 12 :)

5. Tenere sotto controllo i numeri di revisione durante lo sviluppo

E infine ... chiamando

adb uninstall <yourpackagename>

disinstalla completamente l'app. Quando installi di nuovo, hai la certezza di colpire, il onCreateche ti impedisce di continuare ad aumentare la versione del database nella stratosfera mentre sviluppi ...


5
Per quanto riguarda # 4: non sarebbe un'idea migliore utilizzare l' oldVersionargomento passato? Se le istruzioni di aggiornamento sono ripetibili, potresti finire per ripeterle su un database per lo più aggiornato. Se una delle affermazioni è troncare una tabella, sarebbe molto brutto.
Greyson

3
@ Greyson: ottimo punto! Onestamente, mi sento un po 'stupido per non averci mai pensato davvero. A volte penso che prendiamo l'abitudine di usare gli argomenti che vogliamo e ignorare il resto!
jkschneider

1
Sei tu che controlli il database, perché dovresti cambiare il nome?
jkschneider

3
newVersionè un po 'inutile, poiché imposti sempre la versione corrente del database comunque nel costruttore (vedi parte 2) e corrisponderà sempre. L'idea chiave qui è che non vuoi solo aggiornare da dove si trova l'utente newVersionsenza passare attraverso ogni altro aggiornamento incrementale nel mezzo.
jkschneider

2
@kai La CREATE_READINGSlogica non dovrebbe mai essere in onUpgrade, poiché era nel onCreatemetodo della tua prima versione. Pensa ai casi nello onUpgradeswitch come "Aggiornamento DA oldVersion". Non creeresti la tabella delle letture se eseguissi l'aggiornamento dalla versione 1, poiché dovrebbe già esistere. Spero che questo abbia senso ...
jkschneider

9

Il tuo codice sembra corretto. Il mio suggerimento è che il database pensi già di essere aggiornato. Se hai eseguito il progetto dopo aver incrementato il numero di versione, ma prima di aggiungere la execSQLchiamata, il database sul tuo dispositivo / emulatore di prova potrebbe già credere che sia alla versione 2.

Un modo rapido per verificarlo sarebbe cambiare il numero di versione in 3: se si aggiorna dopo, sai che era solo perché il tuo dispositivo credeva che fosse già aggiornato.


Quindi, come previsto, il tuo codice andava bene; solo non quando è stato eseguito in modo incrementale. Ricorda di aggiungere la creazione della tabella a onCreate()come indicato da jkschneider.
Greyson

2

Puoi usare il onUpgrademetodo di SQLiteOpenHelper . Nel metodo onUpgrade, ottieni oldVersion come uno dei parametri.

Nell'uso onUpgradeae switchin ciascuno di essi caseutilizzare il numero di versione per tenere traccia della versione corrente del database.

È meglio passare da oldVersiona newVersion, incrementando versiondi 1 alla volta e quindi aggiornare il database passo dopo passo. Ciò è molto utile quando qualcuno con la versione del database 1 aggiorna l'app dopo molto tempo, a una versione che utilizza la versione 7 del database e l'app inizia a bloccarsi a causa di alcune modifiche incompatibili.

Quindi gli aggiornamenti nel database verranno eseguiti in modo graduale, coprendo tutti i casi possibili, ad esempio incorporando le modifiche nel database fatte per ogni nuova versione e quindi prevenendo il crash dell'applicazione.

Per esempio:

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

}

Se rimuovi quelle dichiarazioni di interruzione non avrai bisogno di un ciclo
Tash Pemhiwa

ma oldVersion deve aumentare in ogni caso per passare il caso successivo @TashPemhiwa
Beulah Ana

Il motivo per cui un'istruzione switch richiede un'interruzione è che è possibile eseguire più casi contemporaneamente - e questo sarà il caso anche se la condizione del caso non è soddisfatta, @BeulahAna
Tash Pemhiwa

Se aggiungi un'interruzione e alcuni db hanno una versione vecchia o recente, la tua query può essere fallita quindi l'interruzione non è richiesta esempio di alterare la tabella se qualche colonna è già alterata in qualche versione del db, la tua query potrebbe non riuscire secondo la sequenza di perdita della versione del db
Neeraj Singh

2

La risposta di @ jkschneider è giusta. Tuttavia esiste un approccio migliore.

Scrivi le modifiche necessarie in un file sql per ogni aggiornamento come descritto nel link 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) ;

Questi file .sql verranno eseguiti nel metodo onUpgrade () in base alla versione del database.

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 progetto di esempio è fornito nello stesso collegamento anche: https://github.com/riggaroo/AndroidDatabaseUpgrades


1
Stavo per venire qui e scrivere lo stesso consiglio. Sono contento che tu l'abbia già fatto. Le persone dovrebbero assolutamente leggere l'articolo a cui sei collegato. Questo è anche ciò che Android SQLiteAssetHelper consiglia per gli aggiornamenti. È anche ciò che CL. ( l' esperto di SQLite qui su Stack Overflow) consiglia .
Suragch,

Questo commento quello che stavo cercando. Gli script sql, +1
blueware

1

La gestione delle versioni del database è una parte molto importante dello sviluppo dell'applicazione. Presumo che tu abbia già estensione della classe AppDbHelper SQLiteOpenHelper. Quando lo estendi, dovrai implementare onCreatee onUpgrademetodo.

  1. Quando onCreatee onUpgrademetodi chiamati

    • onCreate chiamato quando l'app è stata appena installata.
    • onUpgrade chiamato quando l'app viene aggiornata.
  2. Organizzazione delle versioni del database Gestisco le versioni in metodi di classe. Creare l'implementazione dell'interfaccia Migration. Ad esempio, per la prima versione creare la MigrationV1classe, la seconda versione creare MigrationV1ToV2(queste sono la mia convenzione di denominazione)


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

Esempio di migrazione:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Utilizzo delle classi di migrazione

onCreate: Poiché onCreateverrà richiamato quando l'applicazione è appena installata, è necessario eseguire anche tutte le migrazioni (aggiornamenti della versione del database). Quindi onCreateapparirà così:

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: Questo metodo verrà chiamato quando l'applicazione è già installata e viene aggiornata alla nuova versione dell'applicazione. Se l'applicazione contiene modifiche al database, inserire tutte le modifiche al database nella nuova classe di migrazione e incrementare la versione del database.

Ad esempio, diciamo che l'utente ha installato un'applicazione che ha la versione del database 1 e ora la versione del database è aggiornata a 2 (tutti gli aggiornamenti dello schema conservati MigrationV1ToV2). Ora, quando l'applicazione viene aggiornata, è necessario aggiornare il database applicando le modifiche allo schema del database in MigrationV1ToV2questo modo:

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

Nota: tutti gli aggiornamenti (menzionati in onUpgrade) nello schema del database devono essere eseguiti inonCreate

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.