Android: actualizar la versión de la base de datos y agregar una nueva tabla

117

Ya creé tablas sqlite para mi aplicación, pero ahora quiero agregar una nueva tabla a la base de datos.

Cambié la versión de la base de datos de la siguiente manera

private static final int DATABASE_VERSION = 2;

y cadena agregada para crear la tabla

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

onCreatey onUpgradecomo a continuación:

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

    }

Pero por alguna razón no se está creando la nueva tabla. ¿Qué estoy haciendo mal?

Jay Mayu
fuente
Esta es otra solución interesante, pero hasta ahora la versión más robusta que he visto está aquí .
Suragch

Respuestas:

280

1. Acerca de onCreate () y onUpgrade ()

onCreate(..)se llama cada vez que la aplicación está recién instalada. onUpgradese llama siempre que la aplicación se actualiza y se inicia y la versión de la base de datos no es la misma.

2. Incrementar la versión db

Necesitas un constructor como:

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

IMPORTANTE: ¡Incrementar la versión de la aplicación por sí solo no es suficiente para onUpgradeser llamado!

3. ¡No olvide a sus nuevos usuarios!

No olvide agregar

database.execSQL(DATABASE_CREATE_color);

a su método onCreate () también o las aplicaciones recién instaladas carecerán de la tabla.

4. Cómo lidiar con múltiples cambios en la base de datos a lo largo del tiempo

Cuando tenga actualizaciones sucesivas de la aplicación, varias de las cuales tienen actualizaciones de base de datos, debe asegurarse de verificar 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 esta forma, cuando un usuario actualiza de la versión 1 a la versión 3, obtiene ambas actualizaciones. Cuando un usuario actualiza de la versión 2 a la 3, solo recibe la actualización de la revisión 3 ... Después de todo, no puede contar con el 100% de su base de usuarios para actualizar cada vez que lance una actualización. A veces se saltan una actualización o 12 :)

5. Mantener sus números de revisión bajo control mientras desarrolla

Y finalmente ... llamando

adb uninstall <yourpackagename>

desinstala totalmente la aplicación. Cuando lo instales de nuevo, tienes la garantía de acertar, lo onCreateque evita que tengas que seguir incrementando la versión de la base de datos en la estratosfera a medida que desarrollas ...

jkschneider
fuente
5
Respecto al # 4: ¿No sería una mejor idea utilizar el oldVersionargumento pasado? Si alguna declaración de actualización es repetible, es posible que termine repitiéndola en una base de datos en su mayoría actualizada. Si una de las declaraciones es truncar una tabla, sería muy malo.
Greyson
3
@Greyson: ¡Gran punto! Honestamente, me siento un poco tonto por nunca pensar en eso. ¡A veces creo que nos acostumbramos a usar los argumentos que queremos e ignorar el resto!
jkschneider
1
Tú controlas la base de datos, ¿por qué cambiarías el nombre?
jkschneider
3
newVersiones un poco inútil, ya que siempre establece la versión actual de la base de datos de todos modos en el constructor (consulte la parte 2) y siempre coincidirá. La idea clave aquí es que no desea actualizar desde cualquier lugar al que se encuentre el usuario newVersionsin pasar por cualquier otra actualización incremental en el medio.
jkschneider
2
@kai La CREATE_READINGSlógica nunca debería estar en onUpgrade, ya que estaba en el onCreatemétodo de su primera versión. Piense en los casos en el onUpgradeconmutador como "Estoy actualizando DESDE oldVersion". No crearía la tabla de lecturas si estuviera actualizando desde la versión 1, ya que ya debería existir. Con suerte, esto tiene sentido ...
jkschneider
9

Tu código parece correcto. Mi sugerencia es que la base de datos ya cree que está actualizada. Si ejecutó el proyecto después de incrementar el número de versión, pero antes de agregar la execSQLllamada, es posible que la base de datos de su dispositivo de prueba / emulador ya crea que está en la versión 2.

Una forma rápida de verificar esto sería cambiar el número de versión a 3; si se actualiza después de eso, sabrá que fue solo porque su dispositivo creía que ya estaba actualizado.

Greyson
fuente
Entonces, como se esperaba, su código estuvo bien; pero no cuando se ejecutó de forma incremental. Recuerde agregar la creación de la tabla onCreate()como señaló jkschneider.
Greyson
2

Puede utilizar el onUpgrademétodo de SQLiteOpenHelper . En el método onUpgrade, obtiene oldVersion como uno de los parámetros.

En el onUpgradeuso ay switchen cada uno de los cases use el número de versión para realizar un seguimiento de la versión actual de la base de datos.

Es mejor que pase de oldVersiona newVersion, incrementándose versionen 1 a la vez y luego actualice la base de datos paso a paso. Esto es muy útil cuando alguien con la versión 1 de la base de datos actualiza la aplicación después de mucho tiempo, a una versión que usa la versión 7 de la base de datos y la aplicación comienza a fallar debido a ciertos cambios incompatibles.

Luego, las actualizaciones en la base de datos se realizarán paso a paso, cubriendo todos los casos posibles, es decir, incorporando los cambios en la base de datos realizados para cada nueva versión y evitando así que su aplicación falle.

Por ejemplo:

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
fuente
Si elimina esas declaraciones de interrupción, no necesitará un bucle
Tash Pemhiwa
pero oldVersion tiene que incrementarse en cada caso para aprobar el siguiente caso @TashPemhiwa
Beulah Ana
La razón por la que una declaración de cambio requiere un descanso es que es posible ejecutar varios casos a la vez, y este será el caso incluso si no se cumple la condición del caso, @BeulahAna
Tash Pemhiwa
Si agrega un descanso y algunos db tienen una versión antigua o reciente, entonces su consulta puede fallar, por lo que no se requiere un salto, por ejemplo, modificar la tabla si alguna columna ya se alteró en alguna versión de db, entonces su consulta puede fallar según la secuencia de pérdida de la versión de db
Neeraj Singh
2

La respuesta de @jkschneider es correcta. Sin embargo, existe un mejor enfoque.

Escriba los cambios necesarios en un archivo sql para cada actualización como se describe en el enlace 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) ;

Estos archivos .sql se ejecutarán en el método onUpgrade () de acuerdo con la versión de la base de datos.

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

También se proporciona un proyecto de ejemplo en el mismo enlace: https://github.com/riggaroo/AndroidDatabaseUpgrades

oiyio
fuente
1
Estaba a punto de venir aquí y escribir el mismo consejo. Me alegro de que ya lo hayas hecho. La gente definitivamente debería leer el artículo al que enlazaste. Esto también es lo que recomienda Android SQLiteAssetHelper para las actualizaciones. También es lo que CL. ( el experto en SQLite aquí en Stack Overflow) recomienda .
Suragch
Este comentario lo que estaba buscando. Los scripts sql, +1
blueware
1

El manejo de las versiones de la base de datos es una parte muy importante del desarrollo de aplicaciones. Supongo que ya tienes la clase AppDbHelper extendiéndose SQLiteOpenHelper. Cuando se amplía que tendrá que poner en práctica onCreatey onUpgrademétodo.

  1. Cuándo onCreatey onUpgrademétodos llamados

    • onCreate llamado cuando la aplicación se instaló recientemente.
    • onUpgrade llamado cuando la aplicación se actualizó.
  2. Organización de las versiones de la base de datos Administro las versiones en una clase de métodos. Crear implementación de migración de interfaz. Por ejemplo, para la primera versión, crear la MigrationV1clase, la segunda versión, crear MigrationV1ToV2(estas son mi convención de nomenclatura)


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

Ejemplo de migración:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Usar clases de migración

onCreate: Dado onCreateque se llamará cuando la aplicación esté recién instalada, también necesitamos ejecutar todas las migraciones (actualizaciones de la versión de la base de datos). Así se onCreateverá así:

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: Este método se llamará cuando la aplicación ya esté instalada y se actualice a la nueva versión de la aplicación. Si la aplicación contiene cambios en la base de datos, coloque todos los cambios en la nueva clase de migración e incremente la versión de la base de datos.

Por ejemplo, digamos que el usuario ha instalado una aplicación que tiene la versión de base de datos 1, y ahora la versión de la base de datos se actualiza a 2 (todas las actualizaciones de esquema se mantienen MigrationV1ToV2). Ahora, cuando la aplicación se actualizó, necesitamos actualizar la base de datos aplicando cambios en el esquema de la base de datos de MigrationV1ToV2esta manera:

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: Todas las actualizaciones (mencionadas en onUpgrade) en el esquema de la base de datos deben ejecutarse enonCreate

VIjay J
fuente