一.SQLite簡介和常用語法
二.數(shù)據(jù)庫創(chuàng)建,升級及降級
上篇文章簡介和常用語法介紹了SQLite數(shù)據(jù)庫的基本信息和一些常用的語法操作,本篇文章主要介紹Android開發(fā)過程中SQLite數(shù)據(jù)庫的創(chuàng)建使用和常見問題處理。
一.SQLiteOpenHelper介紹
對于Android平臺來說,我們可以使用系統(tǒng)提供的API輕松實(shí)現(xiàn)對SQLite數(shù)據(jù)庫的操作
Android提供SQLiteOpenHelper類以便我們創(chuàng)建操作數(shù)據(jù)庫。通常情況下我們會新建一個SQLiteOpenHelper的子類作為一個數(shù)據(jù)庫的操作類。
數(shù)據(jù)庫操作類示例 :
DatabaseHelper
public class DatabaseHelper extends SQLiteOpenHelper {
/**
* 數(shù)據(jù)庫名稱
*/
private static final String DATABASE_NAME = "account.db";
/**
* 數(shù)據(jù)庫版本-升級用
*/
private static final int DATABASE_VERSION = 1;
public DatabaseHelper(Context context) {
//CursorFactory設(shè)置為null,使用默認(rèn)值
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* 數(shù)據(jù)庫第一次創(chuàng)建時調(diào)用
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS account " +
"(userId VERCHAR PRIMARY KEY,userName VERCHAR,totalAccount INTEGER)");
}
/**
* @param db 數(shù)據(jù)庫實(shí)際操作類
* @param oldVersion 舊版本
* @param newVersion 新版本
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
二.數(shù)據(jù)庫創(chuàng)建-構(gòu)造方法
數(shù)據(jù)庫的創(chuàng)建以及版本,是由SQLiteOpenHelper的構(gòu)造方法參數(shù)決定的。
SQLiteOpenHelper 有三個構(gòu)造構(gòu)造方法:
/**
* @param context 用來打開或者創(chuàng)建數(shù)據(jù)庫
* @param name 數(shù)據(jù)庫文件名稱或者傳null為一個內(nèi)存數(shù)據(jù)庫
* @param factory 用來創(chuàng)建cursor對象或者傳null使用默認(rèn)的
* @param version 數(shù)據(jù)庫版本號 (從1開始);如果數(shù)據(jù)庫版本比當(dāng)前版舊,onUpgrade方法會被調(diào)用來更新數(shù)據(jù)庫,
如果比當(dāng)前新onDowngrade方法會被調(diào)用來降級數(shù)據(jù)庫
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
/**
* @param errorHandler DatabaseErrorHandler數(shù)據(jù)庫損壞時回調(diào).
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory,
int version, DatabaseErrorHandler errorHandler) {
this(context, name, factory, version, 0, errorHandler);
}
/**
* @param minimumSupportedVersion 支持調(diào)用onUpgrade更新到version版本的最版本號,
如果當(dāng)前版本比最小支持的版本還下數(shù)據(jù)庫就會刪除然后重新創(chuàng)建version版本的數(shù)據(jù)庫。
在刪除前onBeforeDelete會被調(diào)用我們可以在此方法中做一些處理。minimumSupportedVersion 默認(rèn)為0
* @hide
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory,
int version, int minimumSupportedVersion, DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " +
version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
}
最終都會調(diào)用第三個構(gòu)造方法,方法中判斷如果version 小于1會拋出異常,所以Android SQLite數(shù)據(jù)庫版本必須從1開始。實(shí)際上最后一個構(gòu)造方法被@hide注解了,所以一般情況下在子類中我們是沒有辦法主動調(diào)用的,只能調(diào)用前兩個構(gòu)造方法,minimumSupportedVersion 也始終是0。
三.數(shù)據(jù)庫首次創(chuàng)建回調(diào)-onCreate方法
數(shù)據(jù)庫首次創(chuàng)建的時候onCreate方法會被執(zhí)行。表的創(chuàng)建和初始化需要在方法中進(jìn)行。這個是抽象方法,實(shí)體類中必須實(shí)現(xiàn)。實(shí)現(xiàn)很簡單就是做一些創(chuàng)建表的操作。
示例:
/**
* 數(shù)據(jù)庫第一次創(chuàng)建時調(diào)用
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS account " +
"(userId VERCHAR PRIMARY KEY,userName VERCHAR,totalAccount INTEGER)");
}
四.數(shù)據(jù)庫的升級-onUpgrade方法
/**
* @param db The database.
* @param oldVersion 舊數(shù)據(jù)庫版本
* @param newVersion 新數(shù)據(jù)庫版本
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
當(dāng)數(shù)據(jù)庫升級時(當(dāng)前數(shù)據(jù)庫版本大于已創(chuàng)建的數(shù)據(jù)庫版本)會執(zhí)行此方法,首次創(chuàng)建時不會執(zhí)行。這是個抽象方法,在我們的實(shí)體類中必須實(shí)現(xiàn)。我們可以在這個方法中根據(jù)數(shù)據(jù)庫版本做一些刪除表,創(chuàng)建表,新增表字段或者重命名表等操作保證數(shù)據(jù)庫與最新版本數(shù)據(jù)庫同步。
常用數(shù)據(jù)庫升級操作:
數(shù)據(jù)庫的升級一般有三種方式如下:
1.onCreate方法始終保證是最新的,在onUpgrade方法中刪除所有已存在表,在調(diào)用onCreate中創(chuàng)建表的方法重新創(chuàng)建所有表,保證表是最新的。(當(dāng)然如果你想保存數(shù)據(jù)也可以先將數(shù)據(jù)保存到內(nèi)存再插入,如果表很多或者數(shù)據(jù)量很大的是不建議怎么操作的,非常影響性能)
2.onCreate方法始終保證是最新的,再onUpgrade方法中根據(jù)版本號判斷執(zhí)行創(chuàng)建新表或者更新字段等操作
版本號需要 判斷上下限:
上限(為了再從當(dāng)前版本x更新到新版本y時不重復(fù)執(zhí)行更新操作):假如我們要升級到新版本x,是我們的當(dāng)前版本x,需要小于當(dāng)前版本號x。
下限:如果是創(chuàng)建新表下限就是當(dāng)前版本之前的所有版本,如果是更新表字段或者重命名表下限是表創(chuàng)建的版本需要大于等于下限版本
- 舉個栗子:
- 2.1. 在上面的示例我們創(chuàng)建了版本1數(shù)據(jù)庫,onCreate方法創(chuàng)建了表account,現(xiàn)在需要在表account中新增字段subAccountId并新增一個表subAccount。
按照上面的方式我們需要更新表版本號為假設(shè)為2(可以為更大的,不要求連續(xù),連續(xù)方便之后的版本更新迭代),在onCreate方法中修改原來創(chuàng)建表account的SQL新增字段subAccountId,同時新增創(chuàng)建表subAccount的SQL并執(zhí)行。
然后在onUpgrade中判斷oldVersion版本號大于等于原版本1小于現(xiàn)版本2,執(zhí)行新增字段和新增表SQL。
具體代碼如下:
public class DatabaseHelper extends SQLiteOpenHelper {
/**
* 數(shù)據(jù)庫名稱
*/
private static final String DATABASE_NAME = "account.db";
/**
* 數(shù)據(jù)庫版本-升級用
*/
private static final int DATABASE_VERSION = 2;
public DatabaseHelper(Context context) {
//CursorFactory設(shè)置為null,使用默認(rèn)值
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* 數(shù)據(jù)庫第一次創(chuàng)建時調(diào)用
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS account " +
"(userId VERCHAR PRIMARY KEY,userName VERCHAR,totalAccount INTEGER," +
"subAccountId VARCHAR default '')");
db.execSQL("CREATE TABLE IF NOT EXISTS subAccount " +
"(userId VERCHAR PRIMARY KEY,subAccountId VERCHAR,totalAccount INTEGER)");
}
/**
* @param db 數(shù)據(jù)庫實(shí)際操作類
* @param oldVersion 舊版本
* @param newVersion 新版本
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion >= 1 && oldVersion < 2) {
db.execSQL("ALTER TABLE account ADD COLUMN subAccountId VARCHAR default ''");
db.execSQL("CREATE TABLE IF NOT EXISTS subAccount " +
"(userId VERCHAR PRIMARY KEY,subAccountId VERCHAR,totalAccount INTEGER)");
}
}
}
- 2.2 假設(shè)這個時候,還需要升級數(shù)據(jù)庫到版本3,在表subAccount 中新增個字段userName。
onCreate方法里修改就不說了創(chuàng)建表subAccount 加上userName字段就行。onUpgrade方法中怎么判斷版本號,按照上面的說明可知上限是小于3,下限則是大于等于表創(chuàng)建的版本,subAccount 表創(chuàng)建的版本是2所以需要大于等于2,而不是大于等于初始版本。
具體代碼如下:
/**
* 數(shù)據(jù)庫版本-升級用
* 版本2:新增數(shù)據(jù)庫subAccount
*/
private static final int DATABASE_VERSION = 3;
public DatabaseHelper(Context context) {
//CursorFactory設(shè)置為null,使用默認(rèn)值
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* 數(shù)據(jù)庫第一次創(chuàng)建時調(diào)用
*/
@Override
public void onCreate(SQLiteDatabase db) {
createAccountTable(db);
createSubAccountTable(db);
}
/**
* 創(chuàng)建subAccount表(最好封裝下,保證onCreate和onUpgrade方法中創(chuàng)建的都是最新表)
*/
private void createSubAccountTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS subAccount " +
"(userId VERCHAR PRIMARY KEY,subAccountId VERCHAR,totalAccount INTEGER," +
"userName VARCHAR default '')");
}
private void createAccountTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS account " +
"(userId VERCHAR PRIMARY KEY,userName VERCHAR,totalAccount INTEGER," +
"subAccountId VARCHAR default '')");
}
/**
* @param db 數(shù)據(jù)庫實(shí)際操作類
* @param oldVersion 舊版本
* @param newVersion 新版本
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion >= 1 && oldVersion < 2) {
db.execSQL("ALTER TABLE account ADD COLUMN subAccountId VARCHAR default ''");
createSubAccountTable(db);
}
if (oldVersion >= 2 && oldVersion < 3) {
db.execSQL("ALTER TABLE subAccount ADD COLUMN userName VARCHAR default ''");
}
}
}
- 升級測試說明:
直接安裝版本3數(shù)據(jù)庫的APP——走onCreate方法創(chuàng)建最新表
安裝版本2--升級版本3APP——onUpgrade只會走升級subAccount 表的邏輯
安裝版本1--升級版本3APP——onUpgrade只會走升級account 表的邏輯和創(chuàng)建表subAccount 的邏輯,創(chuàng)建表subAccount 和onCreate中執(zhí)行的是同義SQL,創(chuàng)建的都是最新表。
這種方式更新數(shù)據(jù)庫一定要注意表的創(chuàng)建版本,所以必要的注釋還是要詳細(xì)點(diǎn)。
3.第三種方式和第二種基本是一樣,只是在onUpgrade方法中一步步往上升級,已上面2.2例子說明,初始版本為1,升級到版本3,先升級到版本2再升級到版本3這樣就不需要判斷oldVersion 版本號的上下限了。
使用這種方式升級,onCreate中可以是最新表創(chuàng)建(3.1示例),也可以只進(jìn)行最初版本的表創(chuàng)建然后調(diào)用onUpgrade方法升級數(shù)據(jù)庫(3.2示例)
- 3.1 onCreate中最新的數(shù)據(jù)表,onUpgrade方法中根據(jù)版本號一步步往上升級
代碼如下:
/**
* 數(shù)據(jù)庫名稱
*/
private static final String DATABASE_NAME = "account.db";
/**
* 數(shù)據(jù)庫版本-升級用
*/
private static final int DATABASE_VERSION = 3;
public DatabaseHelper3(Context context) {
//CursorFactory設(shè)置為null,使用默認(rèn)值
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* 數(shù)據(jù)庫第一次創(chuàng)建時調(diào)用
*/
@Override
public void onCreate(SQLiteDatabase db) {
createAccountTable(db);
createSubAccountTable(db);
}
private void createSubAccountTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS subAccount " +
"(userId VERCHAR PRIMARY KEY,subAccountId VERCHAR,totalAccount INTEGER," +
"userName VARCHAR default '')");
}
private void createAccountTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS account " +
"(userId VERCHAR PRIMARY KEY,userName VERCHAR,totalAccount INTEGER," +
"subAccountId VARCHAR default '')");
}
/**
* @param db 數(shù)據(jù)庫實(shí)際操作類
* @param oldVersion 舊版本
* @param newVersion 新版本
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
int version = oldVersion;
if (version == 1){
db.execSQL("ALTER TABLE account ADD COLUMN subAccountId VARCHAR default ''");
db.execSQL("CREATE TABLE IF NOT EXISTS subAccount " +
"(userId VERCHAR PRIMARY KEY,subAccountId VERCHAR,totalAccount INTEGER)");
version = 2;
}
if (version == 2){
db.execSQL("ALTER TABLE subAccount ADD COLUMN userName VARCHAR default ''");
}
}
- 3.2 onCreate中創(chuàng)建最初版本的表然后調(diào)用onUpgrade方法傳入oldVersion為最初版本,onUpgrade方法中根據(jù)版本號一步步往上升級,需要判斷下版本號oldVersion>=newVersion時不做處理(即初始版本不處理)
代碼如下:
/**
* 數(shù)據(jù)庫名稱
*/
private static final String DATABASE_NAME = "account.db";
/**
* 最初版本
*/
private static final int FIRST_DATABASE_VERSION = 1;
/**
* 數(shù)據(jù)庫版本-升級用
*/
private static final int DATABASE_VERSION = 3;
public DatabaseHelper2(Context context) {
//CursorFactory設(shè)置為null,使用默認(rèn)值
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* 數(shù)據(jù)庫第一次創(chuàng)建時調(diào)用
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS account " +
"(userId VERCHAR PRIMARY KEY,userName VERCHAR,totalAccount INTEGER)");
onUpgrade(db,FIRST_DATABASE_VERSION,DATABASE_VERSION);
}
/**
* @param db 數(shù)據(jù)庫實(shí)際操作類
* @param oldVersion 舊版本
* @param newVersion 新版本
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion >= newVersion){
return;
}
int version = oldVersion;
if (version == 1){
db.execSQL("ALTER TABLE account ADD COLUMN subAccountId VARCHAR default ''");
db.execSQL("CREATE TABLE IF NOT EXISTS subAccount " +
"(userId VERCHAR PRIMARY KEY,subAccountId VERCHAR,totalAccount INTEGER)");
version = 2;
}
if (version == 2){
db.execSQL("ALTER TABLE subAccount ADD COLUMN userName VARCHAR default ''");
}
}
或者直接判斷小于也是可以的,思路和上面是完全一樣的,也是一步步往上升級,僅僅是寫法不一樣,隨變選一個寫法就行,示例代碼如下:
/**
* @param db 數(shù)據(jù)庫實(shí)際操作類
* @param oldVersion 舊版本
* @param newVersion 新版本
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion >= newVersion){
return;
}
if (oldVersion < 2){
db.execSQL("ALTER TABLE account ADD COLUMN subAccountId VARCHAR default ''");
db.execSQL("CREATE TABLE IF NOT EXISTS subAccount " +
"(userId VERCHAR PRIMARY KEY,subAccountId VERCHAR,totalAccount INTEGER)");
}
if (version < 3){
db.execSQL("ALTER TABLE subAccount ADD COLUMN userName VARCHAR default ''");
}
}
總結(jié):
以上三種方式均可以實(shí)現(xiàn)數(shù)據(jù)庫版本的升級。
如果數(shù)據(jù)庫版本升級不需要保存舊表數(shù)據(jù)可以選擇方式1升級數(shù)據(jù)庫,刪除所有表重新創(chuàng)建。
如果數(shù)據(jù)庫版本升級需要保存數(shù)據(jù)庫表數(shù)據(jù),則可以選擇方式二或者方式三。
個人比較建議選擇方式二,新安裝APP創(chuàng)建數(shù)據(jù)庫不會做很多多余的操作且邏輯也比較清晰。
當(dāng)然大家可以根據(jù)各自的需求選擇,如果數(shù)據(jù)庫修改不是很頻繁而且修改較多的情況下,方式三也不失為一個好的選擇,代碼邏輯很清晰。
五.數(shù)據(jù)庫降級-onDowngrade方法
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
throw new SQLiteException("Can't downgrade database from version " +
oldVersion + " to " + newVersion);
}
- 降級方法,當(dāng)數(shù)據(jù)庫降級時(當(dāng)前數(shù)據(jù)庫版本小于已創(chuàng)建的數(shù)據(jù)庫版本)執(zhí)行此方法。
- onDowngrade方法是有默認(rèn)實(shí)現(xiàn)的,我們可以看到默認(rèn)實(shí)現(xiàn)就是拋出一個降級異常,所以如果我們不重寫此方法,APP 數(shù)據(jù)庫是沒法往下降級的,安裝的時候會直接崩潰。除非你卸載了重裝,這樣相當(dāng)于首次創(chuàng)建數(shù)據(jù)庫,走onCreate方法。
- 我們可以根據(jù)實(shí)際需求來決定是否需要重寫onDowngrade方法,如果應(yīng)用降級覆蓋安裝不涉及數(shù)據(jù)庫降級或者是不允許降級就不需要重寫此方法,強(qiáng)制用戶安裝最新的。如果有需要則可以重寫此方法,一般操作為刪除所有表再調(diào)用onCreate方法重寫創(chuàng)建表。SQLite數(shù)據(jù)庫表是不能刪減字段的,這樣做舊版本表可能有冗余字段,但這不影響舊版本操作。
舉個栗子:
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
dropTable(db,"account");
dropTable(db,"subAccount");
onCreate(db);
}
/**
* 刪除表
* @param db 數(shù)據(jù)庫
* @param tableName 表名
*/
public void dropTable(SQLiteDatabase db, String tableName) {
if (TextUtils.isEmpty(tableName)) {
return;
}
db.execSQL("drop table " + tableName);
}