Почему необходимо обновить ВЕРСИЯ НОМЕР базы данных, когда
добавить новый столбец в схему?
Нет необходимости обновлять НОМЕР ВЕРСИИ, скорее это предлагаемый способ внесения структурных изменений на основе проверки НОМЕРА ВЕРСИИ, как указано в коде, по сравнению со значением, хранящимся в поле пользовательская версия заголовок файла базы данных (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 .