這篇文章是基于Android數(shù)據(jù)存儲(三)的基礎(chǔ)上進(jìn)行補(bǔ)充的,主要介紹事務(wù)的使用以及改進(jìn)數(shù)據(jù)庫升級的邏輯。

事務(wù)
事務(wù)(Transaction)是并發(fā)控制的基本單位。所謂事務(wù),它是一個操作序列,這些操作要么都執(zhí)行,要么都不執(zhí)行,它是一個不可分割的工作單位。比如你正在進(jìn)行一次轉(zhuǎn)賬操作,銀行會將轉(zhuǎn)賬的金額先從你的賬戶中扣除,然后再向收款方的賬戶中添加等量的金額。看上去好像沒什么問題吧?可是,如果當(dāng)你賬戶中的金額剛剛被扣除,這時由于一些異常原因?qū)е聦Ψ绞湛钍?,這一部分錢就憑空消失了!當(dāng)然銀行肯定已經(jīng)充分考慮到了這種情況,它會保證扣錢和收款的操作要么一起成功,要么都不會成功,而使用的技術(shù)當(dāng)然就是事務(wù)了。
接下來來看一下如何在Android中使用事務(wù)吧,打開上一篇的DatabaseTest項(xiàng)目,在原來的基礎(chǔ)上進(jìn)行修改。比如Book表中的數(shù)據(jù)已經(jīng)很老了,現(xiàn)在需要全部替換成新的數(shù)據(jù),可以先使用delete()方法將Book表中的數(shù)據(jù)先進(jìn)行刪除,然后再調(diào)用insert()方法將新的數(shù)據(jù)添加進(jìn)Book表中。假如在我們成功刪除舊數(shù)據(jù)準(zhǔn)備添加新數(shù)據(jù)的時候,突然出現(xiàn)異常,那么新數(shù)據(jù)就無法添加到Book表中,Book表中什么數(shù)據(jù)都沒有了,這樣肯定是不行的。我們要保證的是刪除舊數(shù)據(jù)和添加數(shù)據(jù)新數(shù)據(jù)要么一起完成,要么繼續(xù)保留原來的舊數(shù)據(jù)。
在布局中添加一個Replace data按鈕,在MainActivity中綁定Button的點(diǎn)擊監(jiān)聽。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
……
private MyDatabaseHelper databaseHelper;
private ContentValues values = new ContentValues();
@Override
protected void onCreate(Bundle savedInstanceState) {
……
databaseHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
……
case R.id.replaece_data:
db = databaseHelper.getWritableDatabase();
db.beginTransaction();//開啟事務(wù)
try {
db.delete("Book", null, null);
//拋出異常,讓事務(wù)失敗,看看舊數(shù)據(jù)是否會被刪除
if (true) {
throw new NullPointerException();
}
values.clear();
values.put("name", "Game of Thrones");
values.put("author", "George Martin");
values.put("pages", 720);
values.put("price", 20.85);
db.insert("Book", null, values);
db.setTransactionSuccessful();//事務(wù)已經(jīng)執(zhí)行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();//結(jié)束事務(wù)
}
break;
default:
break;
}
}
}
上述代碼就是Android中事務(wù)的標(biāo)準(zhǔn)用法,首先調(diào)用SQLiteDatabase的beginTransaction()方法來開啟一個事務(wù),然后在一個異常捕獲的代碼塊中去執(zhí)行具體的數(shù)據(jù)庫操作,當(dāng)所有的操作都完成之后,調(diào)用setTransactionSuccessful()表示事務(wù)已經(jīng)執(zhí)行成功了,最后在 finally代碼塊中調(diào)用endTransaction()來結(jié)束事務(wù)。
注意觀察,我們在刪除舊數(shù)據(jù)的操作完成后手動拋出了一個NullPointerException,這樣添加新數(shù)據(jù)的代碼就執(zhí)行不到了。不過由于事務(wù)的存在,中途出現(xiàn)異常會導(dǎo)致事務(wù)的失敗,此時舊數(shù)據(jù)應(yīng)該是刪除不掉的,接下來就來驗(yàn)證一下。
現(xiàn)在可以運(yùn)行一下程序并點(diǎn)擊Replacedata按鈕,你會發(fā)現(xiàn),Book表中存在的還是之前的舊數(shù)據(jù)。然后將手動拋出異常的那行代碼去除,再重新運(yùn)行一下程序,此時點(diǎn)擊一下Replace data按鈕就會將Book表中的數(shù)據(jù)替換成新數(shù)據(jù)了。
改進(jìn)數(shù)據(jù)庫升級
上一篇我們也對數(shù)據(jù)庫進(jìn)行了升級,不過對升級的處理有些簡單粗暴了。我們?yōu)榱私o數(shù)據(jù)庫添加一張新的表,在onUpgrade()方法中把已經(jīng)存在的表先刪除了,然后強(qiáng)制執(zhí)行一遍onCreate()方法。你要知道實(shí)際開發(fā)中這樣的處理是不行的。想象以下場景,比如你編寫的某個應(yīng)用已經(jīng)成功上線,并且還擁有了不錯的下載量?,F(xiàn)在由于添加新功能的原因,使得數(shù)據(jù)庫也需要一起升級,然后用戶更新了這個版本之后發(fā)現(xiàn)以前程序中存儲的本地?cái)?shù)據(jù)全部丟失了!那么很遺憾,你的用戶群體可能已經(jīng)流失一大半了。
難道產(chǎn)品發(fā)布出去之后還不能升級數(shù)據(jù)庫了?當(dāng)然不是,其實(shí)只需要進(jìn)行一些合理的控制,就可以保證在升級數(shù)據(jù)庫的時候數(shù)據(jù)并不會丟失了。下面就來學(xué)習(xí)一下如何實(shí)現(xiàn)這樣的功能,每一個數(shù)據(jù)庫版本都會對應(yīng)一個版本號, 當(dāng)指定的數(shù)據(jù)庫版本號大于當(dāng)前數(shù)據(jù)庫版本號的時候, 就會進(jìn)入到onUpgrade()方法中去執(zhí)行更新操作。這里需要為每一個版本號賦予它各自改變的內(nèi)容,然后在onUpgrade()方法中對當(dāng)前數(shù)據(jù)庫的版本號進(jìn)行判斷,再執(zhí)行相應(yīng)的改變就可以了。
下面就來看看如何實(shí)現(xiàn)合理升級數(shù)據(jù)庫的操作,打開MyDatabaseHelper類進(jìn)行修改。假如說第一個版本的數(shù)據(jù)庫只有一張Book表,那么這樣寫就可以實(shí)現(xiàn)了
public class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String CREATE_BOOK = "create table Book(" +
"id integer primary key autoincrement," +
"name text," +
"author text," +
"pages integer," +
"price real)" ;
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
this.context = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
Toast.makeText(context, "Database Created succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
以上代碼就可以實(shí)現(xiàn)Book表的創(chuàng)建了,假如在第2個版本的數(shù)據(jù)庫中需要加入Category表,那么只需要將代碼這樣修改即可。
public class MyDatabaseHelper extends SQLiteOpenHelper {
……
private static final String CREATE_CATEGORY = "create table Category(" +
"id integer primary key autoincrement," +
"category_name text," +
"category_cade integer)";**
……
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(context, "Database Created succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
}
這樣就可以實(shí)現(xiàn)Category表的添加而不影響B(tài)ook表的數(shù)據(jù)了,為什么在onCreate()方法和onUpgrade()方法都完成Category表的創(chuàng)建。原因很簡單,假如你是在APP中的數(shù)據(jù)庫升級為第2個版本之后才注冊的新用戶,那么你一開始創(chuàng)建的數(shù)據(jù)庫就是第2個版本的數(shù)據(jù)庫,那么你在創(chuàng)建數(shù)據(jù)庫的時候就會調(diào)用onCreate()方法,這樣就會同時創(chuàng)建Bookb表和Category表;但是那些一開始就是用這個APP的用戶就不會再執(zhí)行onCreate()方法了,因?yàn)閿?shù)據(jù)庫已經(jīng)存在了,他們只會從第1個版本升級為第2個版本,所以在onUpgrade()中也需要實(shí)現(xiàn)創(chuàng)建Category表,這樣才能老用戶在不丟失數(shù)據(jù)的情況下升級到與新用戶的相同的數(shù)據(jù)庫。
需要注意的是每個case語句最后都不加上break,這樣不管你是從哪個版本升級到最新的數(shù)據(jù)庫,都不會缺失其中每一次的升級。比如說你從第1個版本升級到第4個版本,那么你不僅僅執(zhí)行case1就可以了,你還要執(zhí)行case2和case3才會升級到和目標(biāo)相同的數(shù)據(jù)庫。
如果文章對你有所幫助,那么請您點(diǎn)一下?
由于本人水平有限,如有錯誤,歡迎大家指正。如果你在操作過程中發(fā)現(xiàn)一些沒有講到的錯誤或者問題,歡迎在評論留言,一起探討,共同學(xué)習(xí)進(jìn)步!