Android: обновление версии БД и добавление новой таблицы

118

Я уже создал таблицы 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);

    }

Но почему-то новая таблица не создается. Что я делаю не так?

Джей Маю
источник
Это еще одно интересное решение, но до сих пор самый надежный вариант я видел здесь .
Suragch

Ответы:

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что избавит вас от необходимости постоянно увеличивать версию базы данных в стратосфере по мере разработки ...

jkschneider
источник
5
Относительно №4: не было бы лучше использовать oldVersionпереданный аргумент? Если какие-либо операторы обновления повторяются, вы можете в конечном итоге повторить их в наиболее актуальной базе данных. Если одним из операторов является усечение таблицы, это будет очень плохо.
Грейсон
3
@Greyson: Отличный момент! Честно говоря, я чувствую себя немного глупо из-за того, что никогда не задумывался об этом. Иногда мне кажется, что мы привыкли использовать нужные аргументы и игнорировать остальные!
jkschneider
1
Вы контролируете базу данных, зачем вам менять название?
jkschneider 01
3
newVersionэто бесполезно, так как вы всегда устанавливаете текущую версию базы данных в конструкторе (см. часть 2), и она всегда будет соответствовать. Ключевая идея здесь заключается в том, что вы не хотите просто обновляться оттуда, где находится пользователь, newVersionбез прохождения всех других дополнительных обновлений между ними.
jkschneider
2
@kai CREATE_READINGSЛогика никогда не должна быть в onUpgrade, поскольку она была в onCreateметоде вашей первой версии. Думайте о случаях в onUpgradeкоммутаторе как о «Я выполняю обновление с oldVersion». Вы бы не создавали таблицу показаний, если бы выполняли обновление с версии 1, поскольку она уже должна существовать. Надеюсь, это имеет смысл ...
jkschneider
9

Ваш код выглядит правильно. Я предполагаю, что база данных уже думает, что она обновлена. Если вы выполнили проект после увеличения номера версии, но до добавления execSQLвызова, база данных на вашем тестовом устройстве / эмуляторе может уже полагать, что это версия 2.

Быстрый способ проверить это - изменить номер версии на 3 - если он обновится после этого, вы знаете, что это произошло только потому, что ваше устройство считало, что оно уже было обновлено.

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

Вы можете использовать onUpgradeметод SQLiteOpenHelper . В методе onUpgrade вы получаете oldVersion как один из параметров.

При 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;
    }

}
Виджеш Джат
источник
Если вы удалите эти операторы break, цикл вам не понадобится,
Таш Пемхива,
но oldVersion должен увеличиваться в каждом случае, чтобы пройти следующий случай @TashPemhiwa
Beulah Ana
Причина, по которой оператор switch требует перерыва, заключается в том, что можно запускать несколько вариантов одновременно - и это будет так, даже если условие case не выполнено, @BeulahAna
Таш Пемхива
Если вы добавляете break, а некоторые базы данных имеют старую или последнюю версию, ваш запрос может быть неудачным, поэтому разрыв не требуется. Пример изменения таблицы, если какой-то столбец уже изменен в какой-либо версии базы данных, тогда ваш запрос может быть неудачным в соответствии с последовательностью потерь версии
базы данных
2

Ответ @jkschneider правильный. Однако есть подход получше.

Запишите необходимые изменения в файл sql для каждого обновления, как описано по ссылке 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) ;

Эти файлы .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

oiyio
источник
1
Я как раз собирался приехать сюда и написать тот же совет. Я рада, что ты это уже сделал. Людям обязательно стоит прочитать статью, на которую вы ссылаетесь. Это также то, что Android SQLiteAssetHelper рекомендует для обновлений. Это также то, что CL. ( эксперт по SQLite здесь, в Stack Overflow) рекомендует .
Suragch
Этот комментарий то, что я искал. Скрипты sql, +1
blueware
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

Виджай Дж.
источник