【譯】處理 Android SQLite onUpgrade()

原始鏈接

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ù)筐带!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末今穿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子伦籍,更是在濱河造成了極大的恐慌蓝晒,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帖鸦,死亡現(xiàn)場離奇詭異芝薇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)作儿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門洛二,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事灭红÷绿玻” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵变擒,是天一觀的道長君珠。 經(jīng)常有香客問我,道長娇斑,這世上最難降的妖魔是什么策添? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮毫缆,結(jié)果婚禮上唯竹,老公的妹妹穿的比我還像新娘。我一直安慰自己苦丁,他們只是感情好浸颓,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旺拉,像睡著了一般产上。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛾狗,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天晋涣,我揣著相機(jī)與錄音,去河邊找鬼沉桌。 笑死谢鹊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的留凭。 我是一名探鬼主播佃扼,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冰抢!你這毒婦竟也來了松嘶?” 一聲冷哼從身側(cè)響起艘狭,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤挎扰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后巢音,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遵倦,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年官撼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梧躺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掠哥,靈堂內(nèi)的尸體忽然破棺而出巩踏,到底是詐尸還是另有隱情,我是刑警寧澤续搀,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布塞琼,位于F島的核電站,受9級特大地震影響禁舷,放射性物質(zhì)發(fā)生泄漏彪杉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一牵咙、第九天 我趴在偏房一處隱蔽的房頂上張望派近。 院中可真熱鬧,春花似錦洁桌、人聲如沸渴丸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曙强。三九已至,卻和暖如春途茫,著一層夾襖步出監(jiān)牢的瞬間碟嘴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工囊卜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留娜扇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓栅组,卻偏偏與公主長得像雀瓢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子玉掸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,742評論 25 707
  • “愛笑的孩子運(yùn)氣都不會(huì)太差”刃麸,已不記得這句話是誰說的了,可是確實(shí)那么的有道理啊司浪。 身邊遇到兩個(gè)這樣的例子泊业。兩個(gè)人剛...
    小饅頭0601閱讀 310評論 0 2
  • 每一次飛躍 帶著對新鮮空氣的向往 對那個(gè)隱秘夢想的渴望 躍入龍門就不同… 算了吧 除了散發(fā)著惡臭的橋洞 捕魚人細(xì)密...
    桐萬里閱讀 281評論 0 3
  • 和小朋友聊天,我問:"有一種動(dòng)物啊易,視力很不好吁伺!它是什么動(dòng)物?" 答:"蝦" 小朋友問我:"有一種動(dòng)物,視力和聽力都...
    井小青閱讀 268評論 0 0