Android: оновлення версії DB та додавання нової таблиці


117

Я вже створив таблиці sqlite для свого додатка, але тепер я хочу додати нову таблицю до бази даних.

Я змінив версію БД, як показано нижче

private static final int DATABASE_VERSION = 2;

і додано рядок для створення таблиці

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

onCreateі onUpgradeяк нижче:

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

    }

Але чомусь нова таблиця не створюється. Що я роблю неправильно?


Це ще одне цікаве рішення, але поки що найнадійніша версія, яку я бачив, тут .
Сурагч

Відповіді:


280

1. Про onCreate () та onUpgrade ()

onCreate(..)називається, коли додаток щойно встановлений. onUpgradeвикликається щоразу, коли додаток оновлюється та запускається, і версія бази даних не однакова.

2. Збільшення версії db

Вам потрібен такий конструктор, як:

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

ВАЖЛИВО: Потрібно лише збільшити версію додатка, onUpgradeщоб викликати її!

3. Не забувайте своїх нових користувачів!

Не забудьте додати

database.execSQL(DATABASE_CREATE_color);

до вашого методу onCreate (), а також щойно встановленим додаткам не вистачить таблиці.

4. Як боротися з декількома змінами бази даних з часом

Коли ви маєте послідовні оновлення додатків, кілька з яких мають оновлення бази даних, ви хочете переконатися, що перевірити 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); 
   }
}

Таким чином, коли користувач переходить з версії 1 до версії 3, він отримує обидва оновлення. Коли користувач оновлює версію від 2 до 3, він просто отримує оновлення версії 3 ... Зрештою, ви не можете розраховувати на 100% своєї бази користувачів для оновлення щоразу, коли випускаєте оновлення. Іноді вони пропускають оновлення або 12 :)

5. Тримайте під контролем ваші номери ревізії під час розробки

І нарешті ... дзвонять

adb uninstall <yourpackagename>

повністю видаляє додаток. Коли ви знову встановите, ви гарантовано вдаритесь, onCreateщо не дає вам постійно збільшувати версію бази даних у стратосферу під час розвитку ...


5
Щодо №4: Чи не було б кращою ідеєю використовувати oldVersionпереданий аргумент? Якщо будь-які заяви про оновлення повторюються, ви можете повторити їх в основному оновленій базі даних. Якщо одне із тверджень - це обрізання таблиці, це було б дуже погано.
Грейсон

3
@Greyson: Чудова точка! Чесно кажучи, я відчуваю себе трохи німим за те, що ніколи насправді не замислювався над цим. Іноді я думаю, що ми звикаємо використовувати аргументи, які ми хочемо, і ігноруючи решту!
jkschneider

1
Ви керуєте базою даних, чому б ви змінили ім’я?
jkschneider

3
newVersionначебто марно, оскільки ви завжди встановлюєте поточну версію бази даних у будь-якому випадку в конструкторі (див. частину 2), і вона завжди буде відповідати. Ключова ідея тут полягає в тому, що ви не хочете просто оновлюватись з того місця, де користувач прямий, newVersionне проходячи між собою інше додаткове оновлення між ними.
jkschneider

2
@kai CREATE_READINGSЛогіка ніколи не повинна бути включена в оновлення, оскільки це було в onCreateметоді вашої першої версії. Подумайте про випадки в onUpgradeкомутаторі як "Я модернізую ВІД oldVersion". Ви б не створили таблицю читання, якби ви переходили на версію 1, як вона вже повинна існувати. Сподіваємось, це має сенс ...
jkschneider

9

Ваш код виглядає правильно. Моя пропозиція - база даних вже вважає її оновленою. Якщо ви виконали проект після збільшення номера версії, але перед тим, як додати execSQLвиклик, база даних на вашому тестовому пристрої / емуляторі може вже вважати, що це версія 2.

Швидкий спосіб підтвердити це - змінити номер версії на 3 - якщо він після цього оновиться, ви знаєте, це було лише тому, що ваш пристрій вважав, що він уже оновлений.


Тоді, як і очікувалося, ваш код був нормальним; тільки не тоді, коли воно проходило поступово. Не забудьте додати створення таблиці на onCreate()зразок jkschneider.
Грейсон

2

Можна використовувати onUpgradeметод SQLiteOpenHelper . У методі onUpgrade ви отримуєте старийVersion як один з параметрів.

Під час onUpgradeвикористання a switchі в кожному з cases використовуйте номер версії для відстеження поточної версії бази даних.

Це найкраще , що ви перебирає від oldVersionдо newVersion, що збільшується versionна 1 , в той час , а потім обновити бази даних кроку за кроком. Це дуже корисно, коли хтось із версією бази даних 1 оновить додаток через тривалий час до версії, що використовує базу даних версії 7, і програма починає виходити з ладу через певні несумісні зміни.

Тоді оновлення в базі даних будуть здійснені поетапно, охоплюючи всі можливі випадки, тобто включаючи зміни в базі даних, зроблені для кожної нової версії, і тим самим запобігаючи вашому застосуванню від збоїв.

Наприклад:

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

}

Якщо ви видалите ці заяви про перерву, вам не знадобиться цикл
Tash Pemhiwa

але oldVersion має збільшуватись у кожному випадку, щоб передати наступну справу @TashPemhiwa
Beulah Ana

Причина, що для заяви перемикача потрібна перерва, полягає в тому, що можна запускати кілька справ одночасно - і це буде так, навіть якщо умова справи не буде дотримано, @BeulahAna
Tash Pemhiwa

Якщо ви додаєте перерву, а деякі db мають стару або недавню версію, то ваш запит може бути невдалий, тому розрив не потрібен, наприклад, таблиця змін, якщо якийсь стовпець вже змінюється в якійсь версії db, то ваш запит може бути невдалим відповідно до послідовності втрат версії db
Neeraj Сінгх

2

@ jkschneider відповідь правильна. Однак є кращий підхід.

Запишіть необхідні зміни у файл sql для кожного оновлення, як описано у посиланні https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correly/

від_1_to_2.sql

ALTER TABLE books ADD COLUMN book_rating INTEGER;

від_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) ;

Ці файли .sql будуть виконані методом onUpgrade () відповідно до версії бази даних.

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

Приклад проекту надається за тим же посиланням також: https://github.com/riggaroo/AndroidDatabaseUpgrades


1
Я ось-ось збирався приїхати сюди і написати ту саму пораду. Я радий, що ти вже це зробив. Люди обов'язково повинні прочитати статтю, з якою ви пов’язані. Це також рекомендує Android SQLiteAssetHelper для оновлення. Це також те, що CL. ( SQLite експерт тут на переповнення стека) рекомендує .
Сурагч

Цей коментар, що я шукав. Сценарії sql, +1
програмне забезпечення

1

Обробка версій бази даних є дуже важливою частиною розробки додатків. Я припускаю, що у вас вже є клас AppDbHelper, що розширюється SQLiteOpenHelper. Розширивши його, вам потрібно буде реалізувати onCreateта onUpgradeрозробити метод.

  1. Коли onCreateі onUpgradeметоди називаються

    • onCreate викликається, коли додаток нещодавно встановлений.
    • onUpgrade називається, коли додаток оновлено.
  2. Організація версій бази даних Я управляю версіями методами класу. Створити реалізацію інтерфейсу Migration. Наприклад, для першої версії створення MigrationV1класу, створення другої версії MigrationV1ToV2(це моя умова про іменування)


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

Приклад міграції:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Використання класів міграції

onCreate: Оскільки onCreateбуде викликано, коли додаток щойно встановлено, нам також потрібно виконати всі міграції (оновлення версії бази даних). Так onCreateбуде виглядати так:

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: Цей метод буде викликаний, коли програма вже встановлена ​​та оновлена ​​до нової версії програми. Якщо додаток містить будь-які зміни в базі даних, слід помістити всі зміни бази даних у новий клас міграції та версію бази даних з збільшенням.

Наприклад, скажімо, користувач встановив додаток, який має версію бази даних 1, а тепер версія бази даних оновлена ​​до 2 (усі оновлення схеми зберігаються MigrationV1ToV2). Тепер, коли додаток оновлено, нам потрібно оновити базу даних, застосувавши зміни схеми бази даних приблизно MigrationV1ToV2так:

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

Примітка: Усі оновлення (згадані в onUpgrade) в схемі бази даних повинні виконуватися вonCreate

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.