Почему необходимо обновить ВЕРСИЯ НОМЕР базы данных при добавлении нового столбца в схему? - PullRequest
0 голосов
/ 01 ноября 2018

Я изучаю разработку приложений для Android от Udacity, я проходил курс обучения базам данных, где инструктор сказал, что нам нужно изменить DATABASE_VERSION, когда мы изменим схему нашей базы данных. Это сильно смутило меня, но не нашло решения. Пожалуйста, кто-нибудь объяснит мне это.

Спасибо

Ответы [ 4 ]

0 голосов
/ 02 ноября 2018

Почему необходимо обновить ВЕРСИЯ НОМЕР базы данных, когда добавить новый столбец в схему?

Нет необходимости обновлять НОМЕР ВЕРСИИ, скорее это предлагаемый способ внесения структурных изменений на основе проверки НОМЕРА ВЕРСИИ, как указано в коде, по сравнению со значением, хранящимся в поле пользовательская версия заголовок файла базы данных (4 байта со смещением 60), но не единственным способом. Это удобство.

Это применимо / доступно только при использовании подкласса (расширяющего) класса SQLiteOpenHelper. Вам не нужно использовать такой подкласс, так как вы можете использовать метод openDatabase SQLiteDatabase (в этом случае ни onCreate , ни onUpgrade (или даже редко используемый onDowngrade) ) методы будут вызваны или даже доступны).

С некоторой работой вы можете реализовать альтернативы, такие как процесс, который проверяет / сравнивает структуру с помощью таблицы sqlite_master и прагмы относительно схемы. Вы можете реализовать процесс, который использует таблицу для отслеживания структурных изменений. Вы можете создать пустую базу данных и использовать ее в качестве модели (хотя это может привести к потере дискового пространства).

Например, на основе добавления столбца у вас может быть метод, которому дается таблица и определение столбца, он проверяет, существует ли столбец в таблице (например, проверяя результат из PRAGMA table_info) и если нет, то применяется действие ALTER .

Обычно путаница заключается не в методе onUpgrade , а в неправильном представлении о методе onCreate . То есть метод onCreate не запускается каждый раз при запуске приложения, метод onCreate запускается автоматически только при создании базы данных, то есть один раз, если база данных не будет затем удалена (в в этом случае он будет работать снова).

  • Путаница может быть вызвана тем, что люди часто видят метод onCreate в действии, используемом для инициализации / настройки значений.

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

* * Демонстрация тысяча сорок-девять
  • Код, который работает как базовое приложение, изменяет структуру базы данных каждый раз, когда приложение запускается для имитации ряда выпусков (первые 4 работает тогда структура будет стабильной).

  • Он не предназначен для использования в реальном приложении.

  • Большинство изменений структуры не выполняется с помощью onUpgrade метод. Тем не менее, четвертый прогон меняет структуру (добавляет столбец таблицы) с помощью метода onUpgrade .

Помощник по базам данных (подкласс SQLiteOpenHelper): -

public class TestDBHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "testdb";

    public static final String TBNAME_001 = "test001_table";
    public static final String TBNAME_002 = "test002_table";
    public static final String TBNAME_003 = "test003_table";
    public static final String COL_NAME = "name_column";
    public static final String COL_EXTRA = "extra_cxolumn";

    String TAG = "TESTDBHLPR";

    public TestDBHelper(Context context) {
        //<<<<<<<<<<Note gets database version according to value in MainActivity>>>>>>>>>>
        super(context, DBNAME, null, MainActivity.getDatabaseVersion());
        Log.d(TAG,"CONSTRUCTOR invoked.");
        this.getWritableDatabase(); //<<<<< Force  database open
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(TAG,"ONCREATE invoked.");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int old_version, int new_version) {
        Log.d(TAG,"ONUPGRADE invoked. " +
                "\n\tOld version (valued stored in DB) is " + String.valueOf(old_version) +
                "\n\tNew version (as coded in the App) is " + String.valueOf(new_version)
        );
        if (new_version == 2) {
            db.execSQL(getAlterSQl(TBNAME_003));
        }
    }

    public static String getAlterSQl(String table_name) {
        return "ALTER TABLE " + table_name + " ADD COLUMN " + COL_EXTRA + " TEXT";
    }
}
  • Обратите внимание, что onCreate не делает ничего, кроме записи выходных данных в журнал.
    • Таблицы будут постепенно добавляться при каждом запуске приложения.
  • onUpgrade будет делать что-то только при изменении номера версии на 2.
  • Метод getAlterSQL просто возвращает строку ALTER TABLE ????? ДОБАВИТЬ КОЛОННА ДОПОЛНИТЕЛЬНЫЙ ТЕКСТ
  • Обратите внимание, как версия базы данных получается из MainActivity (чтобы версию можно было изменить на лету для демонстрации).

Ниже приведен код для вызывающего действия MainActivity.java

public class MainActivity extends AppCompatActivity {

    TestDBHelper mDBHlpr; //<<<<<<<<<< Declare the Database Helper (will at this stage be NULL)
    Context mContext;
    static int mDatabaseVersion = 1; //<<<<<<<<<< DBVERSION can be changed on the fly
    String TAG = "MAINACTIVITY";

    @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;

        addNewTable(); //<<<<<<<<<< update database structure if needed

        Log.d(TAG,"Instantiating the Database helper");
        mDBHlpr = new TestDBHelper(this);
        logDBInfo(mDBHlpr.getWritableDatabase());
    }

    public static int getDatabaseVersion() {
        return mDatabaseVersion;
    }

    private void addNewTable() {
        String TAG = "ADDNEWTABLE";
        File db_file = this.getDatabasePath(TestDBHelper.DBNAME);
        if (!db_file.exists()) {
            Log.d(TAG,"Database doesn't exist so exiting.");
            return;
        }
        Log.d(TAG,"Database file exists. Checking for table");
        SQLiteDatabase db = SQLiteDatabase.openDatabase(
                this.getDatabasePath(TestDBHelper.DBNAME).getPath(),
                null,
                SQLiteDatabase.OPEN_READWRITE,
                null
        );
        Log.d(TAG,"Writing Database info (if any) as before any changes");
        logDBInfo(db);
        Log.d(TAG,"Database existed and has been opened");
        String whereclause = "type='table' AND tbl_name LIKE 'test%'";
        Cursor csr = db.query("sqlite_master",null,whereclause,null,null,null,null);
        int row_count = csr.getCount();
        Log.d(TAG,"Extracted " + String.valueOf(row_count) + " application tables");
        csr.close();
        String table_name = "x"; //<<<<<
        switch (row_count) {
            case 0:
                table_name = TestDBHelper.TBNAME_001;
                break;
            case 1:
                table_name = TestDBHelper.TBNAME_002;
                Log.d(TAG,"Adding column " + TestDBHelper.COL_EXTRA + " to table " + TestDBHelper.TBNAME_001);
                db.execSQL(TestDBHelper.getAlterSQl(TestDBHelper.TBNAME_001));
                break;
            case 2:
                table_name = TestDBHelper.TBNAME_003;
                mDatabaseVersion = 2; //<<<<<<<<<< Force onUpgrade
                break;
            default:
                mDatabaseVersion = 2;
        }
        if (table_name.length() < 2) {
            Log.d(TAG,"Database exists but nothing to do");
            return;
        }
        Log.d(TAG,"Creating table " + table_name);
        String crt_sql = "CREATE TABLE IF NOT EXISTS " + table_name + "(" +
                TestDBHelper.COL_NAME + " TEXT" +
                ")";
        db.execSQL(crt_sql);
        Log.d(TAG,"Writing Database info (if any) as after any changes");
        logDBInfo(db);
        db.close();
    }

    private void logDBInfo(SQLiteDatabase db) {
        String TAG = "DBINFO";
        Cursor csr = db.query("sqlite_master",null,null,null,null,null,null);
        while (csr.moveToNext()) {
            String type = csr.getString(csr.getColumnIndex("type"));
            String table_name = csr.getString(csr.getColumnIndex("tbl_name"));
            Log.d(TAG,"Type is " + type + " for table " + table_name);
            if (type.equals("table")) {
                Cursor csr2 = db.rawQuery("PRAGMA table_info(" + table_name + ")",null);
                while (csr2.moveToNext()) {
                    Log.d(TAG,"\n\tTable has a column named " + csr.getString(csr2.getColumnIndex("name")));
                }
                csr2.close();
            }
        }
        csr.close();
    }
}

Когда приложение запускается до создания экземпляра помощника по базам данных, запускается метод addNewTable .

Метод addNewTable делает разные вещи в зависимости от того, что существует в базе данных.

Первый запуск

Если приложение запускается впервые (или база данных была удалена / приложение удалено), метод просто возвращается.

База данных затем создается при создании экземпляра Помощника по базам данных (this.getWritableDatabase(); //<<<<< Force database open).

Таким образом, вызывается метод onCreate , но он ничего не делает по структуре.

Наконец, вызывается метод logDBInfo, который выводит список таблиц (обратите внимание, что при создании базы данных создается таблица android_metadata , поэтому она указана в списке, это таблица для Android, которая содержит языковой стандарт и обычно может игнорироваться ).

Журнал (из-за большого количества добавленных журналов) показывает: -

11-02 18:45:02.689 2066-2066/axtest.axtest D/ADDNEWTABLE: Database doesn't exist so exiting.
11-02 18:45:02.689 2066-2066/axtest.axtest D/MAINACTIVITY: Instantiating the Database helper
11-02 18:45:02.689 2066-2066/axtest.axtest D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:45:02.701 2066-2066/axtest.axtest D/TESTDBHLPR: ONCREATE invoked.
11-02 18:45:02.701 2066-2066/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:45:02.701 2066-2066/axtest.axtest D/DBINFO:    Table has a column named locale

Второй запуск

Поскольку база данных теперь существует, когда вызывается addNewTable , она не возвращается, вместо этого она открывает базу данных ( без использования помощника по базе данных ), затем записывает информацию базы данных (до того, как изменения применяются).

Затем он выполняет запрос к таблице sqlite_master (внутренняя / системная таблица SQLite с данными об элементах) и извлекает строки для таблиц, которые начинаются с test (их не будет) единственными существующими таблицами являются sqlite_master и android_metadata ).

Получено количество строк, равное количеству таблиц. Для второго запуска будет 0.

Параметр / регистр устанавливает соответственно имя таблицы, для этого прогона значение, содержащееся в константе TABLE001 , которое (поскольку длина результирующего значения больше 1) затем используется для создания таблицы .

После создания таблицы информация базы данных заносится в журнал, теперь отображаются новая таблица и база данных закрыта.

Затем создается экземпляр помощника базы данных, onCreate не вызывается, поскольку база данных теперь существует, onUpgrade не вызывается, так как версия будет 1. Наконец, информация базы данных записывается в журнал.

Журнал показывает: -

11-02 18:46:16.009 2109-2109/axtest.axtest D/ADDNEWTABLE: Database file exists. Checking for table
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as before any changes
11-02 18:46:16.013 2109-2109/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:46:16.013 2109-2109/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Database existed and has been opened
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Extracted 0 application tables
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Creating table test001_table
11-02 18:46:16.021 2109-2109/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as after any changes
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:46:16.021 2109-2109/axtest.axtest D/MAINACTIVITY: Instantiating the Database helper
11-02 18:46:16.021 2109-2109/axtest.axtest D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named name_column

Третий прогон

Это похоже на 2-й запуск, за исключением того, что test002_table добавлен, а не test001_table, и в этой таблице tes001_table есть добавленный столбец с именем extra_column .

Журнал показывает: -

11-02 18:50:13.925 2160-2160/axtest.axtest D/ADDNEWTABLE: Database file exists. Checking for table
11-02 18:50:13.929 2160-2160/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as before any changes
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.933 2160-2160/axtest.axtest D/ADDNEWTABLE: Database existed and has been opened
11-02 18:50:13.933 2160-2160/axtest.axtest D/ADDNEWTABLE: Extracted 1 application tables
11-02 18:50:13.937 2160-2160/axtest.axtest D/ADDNEWTABLE: Adding column extra_column to table test001_table
11-02 18:50:13.937 2160-2160/axtest.axtest D/ADDNEWTABLE: Creating table test002_table
11-02 18:50:13.941 2160-2160/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as after any changes
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named extra_column
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO: Type is table for table test002_table
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.941 2160-2160/axtest.axtest D/MAINACTIVITY: Instantiating the Database helper
11-02 18:50:13.941 2160-2160/axtest.axtest D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named extra_column
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO: Type is table for table test002_table
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column

Четвертый забег

Четвертый прогон добавляет еще одну таблицу, а именно test003_table НО теперь меняет версию базы данных с 1 на 2 до создания экземпляра Помощника по базам данных и, таким образом, в конечном итоге вызывает метод onUpGrade , это изменяет test003_table путем добавления столбца, а именно extra_column

Журнал показывает: -

11-02 18:57:00.589 2230-2230/? D/ADDNEWTABLE: Database file exists. Checking for table
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Writing Database info (if any) as before any changes
11-02 18:57:00.593 2230-2230/? D/DBINFO: Type is table for table android_metadata
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named locale
11-02 18:57:00.593 2230-2230/? D/DBINFO: Type is table for table test001_table
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named extra_column
11-02 18:57:00.593 2230-2230/? D/DBINFO: Type is table for table test002_table
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Database existed and has been opened
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Extracted 2 application tables
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Creating table test003_table
11-02 18:57:00.597 2230-2230/? D/ADDNEWTABLE: Writing Database info (if any) as after any changes
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table android_metadata
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named locale
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table test001_table
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named extra_column
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table test002_table
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table test003_table
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.597 2230-2230/? D/MAINACTIVITY: Instantiating the Database helper
11-02 18:57:00.597 2230-2230/? D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:57:00.601 2230-2230/? D/TESTDBHLPR: ONUPGRADE invoked. 
        Old version (valued stored in DB) is 1
        New version (as coded in the App) is 2
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table android_metadata
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named locale
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table test001_table
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named extra_column
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table test002_table
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table test003_table
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named extra_column

При последующих запусках будет использоваться окончательная структура и версия базы данных 2.

  • Если по умолчанию для конструкции switch / case не задана версия 2 и, следовательно, используется версия 1, произойдет сбой, поскольку не определен метод onDownGrade .
0 голосов
/ 01 ноября 2018

Представьте себе этот сценарий:

  1. Вы создали начальную версию своего приложения.

  2. Люди установили ваше приложение, и когда база данных использовалась впервые, был выполнен метод onCreate(), и Android создал базу данных во внутренней памяти (например, test.db).

  3. Теперь вы создали новую функцию, для которой требуется новый столбец (вы изменили схему).

  4. Новые пользователи, которые установят новую версию вашего приложения, будут в порядке, потому что onCreate будет выполняться с этим новым столбцом.

  5. Однако как быть со старыми пользователями, у которых уже была создана база данных в файловой системе?

Вот почему вам нужен механизм для обновления базы данных. Потому что в новой версии вашего приложения у вас есть SQL-запросы для этой новой схемы базы данных. У вас есть запросы с использованием этого нового столбца. Однако у старых пользователей все еще есть старая версия вашей базы данных. Итак, этот новый столбец для них еще не существует.

Механизм обновления очень прост: каждый раз, когда вам нужно обновить базу данных, просто увеличьте ее версию. Android автоматически проверит версию базы данных в файловой системе. Если установленная версия старше текущей базы данных, будет вызвано onUpdate(), а затем вы сможете обновить ее так, как вам нужно:

  • Вы можете просто удалить старые таблицы и создать новую.
  • Вы можете создать новый столбец, не удаляя старые данные.
  • И т.д. ... Это будет зависеть от проекта к проекту ... Это будет зависеть от вашей необходимости. Вы можете никогда не использовать, например.

Если вы еще не выпустили APK, вам не нужно менять версию базы данных (потому что ваше приложение еще никто не устанавливал). Просто продолжайте вносить необходимые изменения. Однако после выпуска приложения вы всегда должны быть особенно внимательны с версией базы данных и с тем, как она обновляется ... Вы всегда должны симулировать тесты с новой установкой и симулировать пользователя, который обновляет приложение ...

0 голосов
/ 02 ноября 2018

Вы всегда должны менять версию, если вы добавили столбцы в таблицу (например).

Тем не менее, в некоторых случаях (тестирование только для новых функций или если вы не заботитесь о потере старых данных) вы также можете принудительно вызвать onUpgrade (), чтобы удалить все и воссоздать его полностью новым вызовом onCreate ().

Но настоящий смысл это:

   @Override
    public void onUpgrade( SQLiteDatabase database, int oldVersion, int newVersion ) 
    {
      if (oldVersion<2)
         {
         database.execSQL(myQueryAlignment)
         }
    }

Для имитации обновления (проверьте, например, работает ли ваш скрипт) возможно с помощью ADV удалите базу данных на эмуляторе, вставьте старую базу данных и переустановите APK ... таким образом (если версия базы данных была увеличена в новом apk), он будет запускаться onUpdate () "искусственным" способом.

0 голосов
/ 01 ноября 2018

В сущности, чтобы изменить схему базы данных в Android, необходимо вызвать метод onUpgrade () вашего класса SQLHelper, и вы должны выполнить все изменения схемы базы данных там. Увеличение номера версии базы данных сообщает системе, что в схему базы данных внесены изменения (например, новый столбец таблицы), и это вызовет метод OnUpgrade () вашего класса SQLHelper.

Вот пример того, как использовать номер версии базы данных. Предположим, что наша текущая версия базы данных - 4.

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion > 1) {
         // the code to upgrade from our initial database to v2
    }
    if (oldVersion > 2) {
         // the code to upgrade from v2 to v3
    }
    if (oldVersion > 3) {
         // the code to upgrade from v3 to v4
    }
}

Так что теперь, если приложение переустанавливается, вызывается onUpgrade (), если текущая версия БД пользователя была ниже 3. И имея старую версию БД базы данных пользователя, мы можем адекватно перейти на новую схему базы данных, не используя никаких данные.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...