Android SQLite OnUpgrade(): 問題
如果你編寫過至少中等規(guī)模的安卓app,你很可能實(shí)現(xiàn)并利用了一個(gè)SQLite數(shù)據(jù)庫隧枫。網(wǎng)上到處都是關(guān)于如何編寫代碼來實(shí)現(xiàn)的例子。盡管許多例子能夠運(yùn)行谓苟,但當(dāng)需要升級并擴(kuò)展app的數(shù)據(jù)庫時(shí)官脓,它們可能有些麻煩。
案例app
在這篇文章中涝焙,我們將想象我們有個(gè)app需要一張單獨(dú)的SQLite表卑笨。這張表叫做Team。它將有下面的列:Id (int), Name (string), City (string), Mascot (string)仑撞。
標(biāo)準(zhǔn) OnUpgrade() 教程
當(dāng)我第一次開始制作安卓app赤兴,我找到了如下的教程(警告:這不是你想要拷貝的代碼Q稀):
public class SQLiteHelper extends SQLiteOpenHelper {
public SQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
//database values
private static final String DATABASE_NAME = "demoApp.db";
private static final int DATABASE_VERSION = 1;
public static final String COLUMN_ID = "_id";
//team table
public static final String TABLE_TEAM = "team";
public static final String COLUMN_MASCOT = "mascot";
public static final String COLUMN_CITY = "city";
public static final String DATABASE_CREATE_TEAM = "create table "
+ TABLE_TEAM + "(" + COLUMN_ID + " integer primary key autoincrement, "
+ COLUMN_NAME + " string, "
+ COLUMN_MASCOT + " string, "
+ COLUMN_CITY + " string);";
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE_TEAM);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEAM);
onCreate(db);
}
}
當(dāng)你處于開發(fā)早期階段時(shí),這份代碼還算好用桶良。你可以簡單地通過改變創(chuàng)建語句和增加版本號來改變表座舍。問題在于每次升級時(shí),表都會(huì)被刪除重建陨帆。這對于生產(chǎn)環(huán)境的app來說并不實(shí)際曲秉。你不會(huì)想要調(diào)用:
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEAM);
在每次發(fā)布一個(gè)新版本app時(shí)對每張表都這樣處理。
稍好些的例子
一些意識(shí)到問題的開發(fā)者可能會(huì)谷歌搜索“android onupgrade add column.”不幸的是疲牵,截止本文編寫為止承二,前6個(gè)結(jié)果中的5個(gè)都是不好的解決方案!你能在這兒纲爸,這兒亥鸠,這兒,這兒和這兒看到這些方案识啦。
它們都建議修改 OnUpdate() 函數(shù)來更好地改變舊版本中的字段參數(shù)负蚊。但是,它們都有一個(gè)問題颓哮。這里有一些改進(jìn)的建議家妆。看看你能不能找到每個(gè)例子的問題题翻。
Bad Example 1
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
db.execSQL(ALTER_USER_TABLE_ADD_USER_SOCIETY);
db.execSQL(ALTER_USER_TABLE_ADD_USER_STREET1);
}
問題: 這些修改語句將在你每次更新app的時(shí)候執(zhí)行揩徊!如果你從版本1升級到版本2腰鬼,它們將運(yùn)行并添加列嵌赠。如果你從2升級到3(且不改變這段代碼),這時(shí)會(huì)再次嘗試增加字段并且爆出一個(gè)錯(cuò)誤熄赡。你可能會(huì)想只在app改變的時(shí)候執(zhí)行這段代碼姜挺,但是這也很容易出錯(cuò),并會(huì)使您遇到與示例3相同的錯(cuò)誤彼硫。
Bad Example 2
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// If you need to add a column
if (newVersion > oldVersion) {
db.execSQL("ALTER TABLE foo ADD COLUMN new_column INTEGER DEFAULT 0");
}
}
問題:這是一個(gè)危險(xiǎn)的例子炊豪,因?yàn)樵S多開發(fā)者只是簡單地拷貝這段代碼。問題在于每次升級時(shí)拧篮,if 條件都是 true词渤,我們將產(chǎn)生和前一個(gè)例子一樣的問題。
Bad Example 3
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
String upgradeQuery = "ALTER TABLE mytable ADD COLUMN mycolumn TEXT";
if (oldVersion == 1 && newVersion == 2)
db.execSQL(upgradeQuery);
}
問題:這個(gè)例子有些難找出問題串绩。想象一下如果用戶從版本1直接升級到版本3會(huì)發(fā)生什么缺虐。他們將完全錯(cuò)過這段升級代碼!這將成為你app的重大問題礁凡。
正確方法
正如我們所見高氮,我們不想在升級時(shí)刪除數(shù)據(jù)庫慧妄。我們也不想假定用戶能每次都嚴(yán)格按順序進(jìn)行升級,我們確實(shí)希望在跑更新腳本時(shí)做一些版本檢測剪芍。想象我們希望升級我們的app到版本2塞淹,使得表增加一個(gè)coach字段。而在版本3罪裹,我們還希望增加一個(gè)stadium字段饱普。 我們可以使用下面的解決方案(你可以安全地使用這段代碼》凰或者改動(dòng)得更容易閱讀和理解后再使用):
public class SQLiteHelper extends SQLiteOpenHelper {
public SQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
//database values
private static final String DATABASE_NAME = "demoApp.db";
private static final int DATABASE_VERSION = 3;
public static final String COLUMN_ID = "_id";
//team table
public static final String TABLE_TEAM = "team";
public static final String COLUMN_MASCOT = "mascot";
public static final String COLUMN_CITY = "city";
public static final String COLUMN_COACH = "coach";
public static final String COLUMN_STADIUM = "stadium";
private static final String DATABASE_CREATE_TEAM = "create table "
+ TABLE_TEAM + "(" + COLUMN_ID + " integer primary key autoincrement, "
+ COLUMN_NAME + " string, "
+ COLUMN_MASCOT + " string, "
+ COLUMN_COACH + " string, "
+ COLUMN_STADIUM + " string, "
+ COLUMN_CITY + " string);";
private static final String DATABASE_ALTER_TEAM_1 = "ALTER TABLE "
+ TABLE_TEAM + " ADD COLUMN " + COLUMN_COACH + " string;";
private static final String DATABASE_ALTER_TEAM_2 = "ALTER TABLE "
+ TABLE_TEAM + " ADD COLUMN " + COLUMN_STADIUM + " string;";
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE_TEAM);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
db.execSQL(DATABASE_ALTER_TEAM_1);
}
if (oldVersion < 3) {
db.execSQL(DATABASE_ALTER_TEAM_2);
}
}
}
現(xiàn)在费彼,app將執(zhí)行其需要的更新語句。無論前一個(gè)版本是什么口芍,也不管升級到的版本是什么箍铲,app將運(yùn)行合適的語句使得app字段得到正確的更新。 還有一點(diǎn)需要注意的是鬓椭,你還需要更改在onCreate中的創(chuàng)建語句颠猴。所以,如果你在更新中添加了一個(gè)字段小染,請將其添加到onCreate函數(shù)中的create語句(對于新用戶)翘瓮,并將其添加到onUpgrade函數(shù)中的更新語句(對于老用戶)。
你知道處理Android數(shù)據(jù)庫遷移的更好的方法嗎裤翩?將你的方法郵件給我們资盅,如果我們覺得不錯(cuò),將把它加入到這篇文章中踊赠。另外呵扛,如果你正在為你即將推出的app項(xiàng)目尋求幫助,可以查看我們的Android開發(fā)服務(wù)筐带!