Android: atualização da versão do banco de dados e adição de nova tabela


117

Já criei tabelas sqlite para meu aplicativo, mas agora quero adicionar uma nova tabela ao banco de dados.

Eu mudei a versão do banco de dados conforme abaixo

private static final int DATABASE_VERSION = 2;

e string adicionada para criar a tabela

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

onCreatee onUpgradecomo abaixo:

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

    }

Mas por algum motivo, a nova tabela não está sendo criada. O que estou fazendo de errado?


Esta é outra solução interessante, mas até agora a versão mais robusta que vi está aqui .
Suragch

Respostas:


280

1. Sobre onCreate () e onUpgrade ()

onCreate(..)é chamado sempre que o aplicativo é instalado recentemente. onUpgradeé chamado sempre que o aplicativo é atualizado e iniciado e a versão do banco de dados não é a mesma.

2. Incrementando a versão db

Você precisa de um construtor como:

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

IMPORTANTE: Incrementar apenas a versão do aplicativo não é suficiente para onUpgradeser chamado!

3. Não se esqueça de seus novos usuários!

Não se esqueça de adicionar

database.execSQL(DATABASE_CREATE_color);

ao método onCreate () ou aplicativos recém-instalados não terão a tabela.

4. Como lidar com várias mudanças no banco de dados ao longo do tempo

Quando você tiver atualizações sucessivas de aplicativos, várias das quais com atualizações de banco de dados, certifique-se 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); 
   }
}

Dessa forma, quando um usuário atualiza da versão 1 para a versão 3, ele obtém as duas atualizações. Quando um usuário atualiza da versão 2 para a 3, ele apenas obtém a atualização da revisão 3 ... Afinal, você não pode contar com 100% de sua base de usuários para atualizar cada vez que você lançar uma atualização. Às vezes, eles pulam uma atualização ou 12 :)

5. Manter seus números de revisão sob controle durante o desenvolvimento

E finalmente ... ligando

adb uninstall <yourpackagename>

desinstala totalmente o aplicativo. Quando você instalar novamente, terá a garantia de acertar, o onCreateque o impede de continuar incrementando a versão do banco de dados para a estratosfera conforme você desenvolve ...


5
Quanto ao nº 4: Não seria uma ideia melhor usar o oldVersionargumento aprovado? Se qualquer instrução de atualização puder ser repetida, você pode acabar repetindo-a em um banco de dados quase sempre atualizado. Se uma das instruções é para truncar uma tabela, isso seria muito ruim.
Greyson

3
@Greyson: Ótimo ponto! Honestamente, me sinto um pouco idiota por nunca ter realmente pensado nisso. Às vezes acho que adquirimos o hábito de usar os argumentos que queremos e ignorar o resto!
jkschneider de

1
Você controla o banco de dados, por que você mudaria o nome?
jkschneider 01 de

3
newVersioné meio inútil, já que você sempre define a versão atual do banco de dados de qualquer maneira no construtor (consulte a parte 2) e sempre corresponderá. A ideia principal aqui é que você não quer apenas atualizar de onde o usuário estiver, newVersionsem passar por todas as outras atualizações incrementais entre elas.
jkschneider

2
@kai A CREATE_READINGSlógica nunca deve estar em onUpgrade, pois estava no onCreatemétodo de sua primeira versão. Pense nos casos do onUpgradeswitch como "Estou atualizando DE oldVersion". Você não criaria a tabela de leituras se estivesse atualizando da versão 1, pois ela já deveria existir. Espero que isso faça sentido ...
jkschneider

9

Seu código parece correto. Minha sugestão é que o banco de dados já pensa que está atualizado. Se você executou o projeto depois de incrementar o número da versão, mas antes de adicionar a execSQLchamada, o banco de dados em seu dispositivo / emulador de teste pode já acreditar que está na versão 2.

Uma maneira rápida de verificar isso seria alterar o número da versão para 3 - se ele for atualizado depois disso, você sabe que foi apenas porque seu dispositivo acreditou que já estava atualizado.


Então, como esperado, seu código estava bom; mas não quando foi executado de forma incremental. Lembre-se de adicionar a criação da tabela onCreate()como jkschneider apontou.
Greyson

2

Você pode usar o onUpgrademétodo SQLiteOpenHelper . No método onUpgrade, você obtém oldVersion como um dos parâmetros.

No onUpgradeuso a switche em cada um dos cases, use o número da versão para controlar a versão atual do banco de dados.

É melhor fazer um loop de oldVersionpara newVersion, incrementando versionem 1 por vez e, em seguida, atualizar o banco de dados passo a passo. Isso é muito útil quando alguém com a versão 1 do banco de dados atualiza o aplicativo depois de um longo tempo, para uma versão que usa a versão 7 do banco de dados e o aplicativo começa a travar devido a certas alterações incompatíveis.

Em seguida, as atualizações no banco de dados serão feitas passo a passo, cobrindo todos os casos possíveis, ou seja, incorporando as alterações no banco de dados feitas a cada nova versão e, assim, evitando que seu aplicativo trave.

Por exemplo:

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 você remover essas instruções de interrupção, você não precisará de um loop
Tash Pemhiwa

mas a versão antiga deve ser incrementada em cada caso para passar no próximo caso @TashPemhiwa
Beulah Ana

O motivo pelo qual uma instrução switch requer uma pausa é que é possível executar vários casos ao mesmo tempo - e isso será o caso mesmo se a condição do caso não for atendida, @BeulahAna
Tash Pemhiwa

Se você adicionar pausa e alguns db têm versão antiga ou recente, em seguida, sua consulta pode ser não tão ruptura não é necessária exemplo ALTER TABLE se alguma coluna já alterar em alguma versão db em seguida, sua consulta pode falhar como por sequência perda de versão db
Neeraj Singh

2

A resposta de @jkschneider está certa. No entanto, existe uma abordagem melhor.

Grave as alterações necessárias em um arquivo sql para cada atualização conforme descrito no 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) ;

Esses arquivos .sql serão executados no método onUpgrade () de acordo com a versão do banco de dados.

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

Um exemplo de projeto também é fornecido no mesmo link: https://github.com/riggaroo/AndroidDatabaseUpgrades


1
Eu estava prestes a vir aqui e escrever o mesmo conselho. Estou feliz que você já fez isso. As pessoas definitivamente deveriam ler o artigo ao qual você ligou. Isso também é o que o Android SQLiteAssetHelper recomenda para atualizações. É também o que CL. ( o especialista em SQLite aqui no Stack Overflow) recomenda .
Suragch

Esse comentário o que eu estava procurando. Os scripts sql, +1
blueware

1

Manipular versões de banco de dados é uma parte muito importante do desenvolvimento de aplicativos. Presumo que você já tenha a classe AppDbHelper estendendo SQLiteOpenHelper. Quando você estendê-lo você precisará implementar onCreatee onUpgrademétodo.

  1. Quando onCreatee onUpgrademétodos chamados

    • onCreate chamado quando o aplicativo é instalado recentemente.
    • onUpgrade chamado quando o aplicativo é atualizado.
  2. Organizando versões de banco de dados Eu gerencio versões em métodos de classe. Criar implementação de migração de interface. Por exemplo, para a MigrationV1classe de criação da primeira versão, criação da segunda versão MigrationV1ToV2(essas são minha convenção de nomenclatura)


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

Migração de exemplo:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Usando classes de migração

onCreate: Como onCreateserá chamado quando o aplicativo for instalado recentemente, também precisamos executar todas as migrações (atualizações da versão do banco de dados). Então onCreateserá parecido com isto:

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 será chamado quando o aplicativo já estiver instalado e for atualizado para a nova versão do aplicativo. Se o aplicativo contiver quaisquer alterações no banco de dados, coloque todas as alterações no banco de dados na nova classe de migração e versão do banco de dados de incremento.

Por exemplo, digamos que o usuário instalou o aplicativo com a versão 1 do banco de dados e agora a versão do banco de dados está atualizada para 2 (todas as atualizações de esquema mantidas MigrationV1ToV2). Agora, quando o aplicativo é atualizado, precisamos atualizar o banco de dados aplicando as mudanças no esquema do banco de dados MigrationV1ToV2como este:

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 as atualizações (mencionadas em onUpgrade) no esquema do banco de dados devem ser executadas emonCreate

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.