android: SQLite 數據庫的最佳實踐

 

6.5.1    使用事務android

 

前面咱們已經知道,SQLite 數據庫是支持事務的,事務的特性能夠保證讓某一系列的操 做要麼所有完成,要麼一個都不會完成。那麼在什麼狀況下才須要使用事務呢?想象如下場 景,好比你正在進行一次轉帳操做,銀行會將轉帳的金額先從你的帳戶中扣除,而後再向收 款方的帳戶中添加等量的金額。看上去好像沒什麼問題吧?但是,若是當你帳戶中的金額剛 剛被扣除,這時因爲一些異常緣由致使對方收款失敗,這一部分錢就憑空消失了!固然銀行 確定已經充分考慮到了這種狀況,它會保證扣錢和收款的操做要麼一塊兒成功,要麼都不會成 功,而使用的技術固然就是事務了。數據庫

接下來咱們看一看如何在 Android 中使用事務吧,仍然是在 DatabaseTest 項目的基礎上 進行修改。好比 Book 表中的數據都已經很老了,如今準備所有廢棄掉替換成新數據,能夠 先使用 delete()方法將 Book 表中的數據刪除,而後再使用 insert()方法將新的數據添加到表中。 咱們要保證的是,刪除舊數據和添加新數據的操做必須一塊兒完成,不然就還要繼續保留原來 的舊數據。修改 activity_main.xml 中的代碼,以下所示:ide

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"學習

android:orientation="vertical" >this

 

……spa

 

 

<Button android:id="@+id/replace_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Replace data"code

/>xml

</LinearLayout>事務

能夠看到,這裏又添加了一個按鈕,用於進行數據替換操做。而後修改 MainActivity 中 的代碼,以下所示:開發

 

public class MainActivity extends Activity {

 

 

private MyDatabaseHelper dbHelper;

 

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

 

 

 

setContentView(R.layout.activity_main);

dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);

……

Button replaceData = (Button) findViewById(R.id.replace_data);

replaceData.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

SQLiteDatabase db = dbHelper.getWritableDatabase();

db.beginTransaction(); // 開啓事務

try {

db.delete("Book", null, null);

if (true) {

// 在這裏手動拋出一個異常,讓事務失敗

throw new NullPointerException();

}

ContentValues values = new ContentValues(); 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(); // 事務已經執行成功

} catch (Exception e) {

e.printStackTrace();

} finally {

db.endTransaction(); // 結束事務

}

}

});

}

 

}

上述代碼就是 Android 中事務的標準用法,首先調用 SQLiteDatabase 的 beginTransaction() 方法來開啓一個事務,而後在一個異常捕獲的代碼塊中去執行具體的數據庫操做,當全部的 操做都完成以後,調用 setTransactionSuccessful()表示事務已經執行成功了,最後在 finally 代碼塊中調用 endTransaction()來結束事務。注意觀察,咱們在刪除舊數據的操做完成後手動 拋出了一個 NullPointerException,這樣添加新數據的代碼就執行不到了。不過因爲事務的存 在,中途出現異常會致使事務的失敗,此時舊數據應該是刪除不掉的。

如今能夠運行一下程序並點擊 Replace data 按鈕,你會發現,Book 表中存在的仍是以前的舊數據。而後將手動拋出異常的那行代碼去除,再從新運行一下程序,此時點擊一下Replace data 按鈕就會將 Book 表中的數據替換成新數據了。

6.5.2    升級數據庫的最佳寫法

 

在 6.4.2 節中咱們學習的升級數據庫的方式是很是粗暴的,爲了保證數據庫中的表是最 新的,咱們只是簡單地在 onUpgrade()方法中刪除掉了當前全部的表,而後強制從新執行了 一遍 onCreate()方法。這種方式在產品的開發階段確實能夠用,可是當產品真正上線了以後 就絕對不行了。想象如下場景,好比你編寫的某個應用已經成功上線,而且還擁有了不錯的 下載量。如今因爲添加新功能的緣由,使得數據庫也須要一塊兒升級,而後用戶更新了這個版 本以後發現之前程序中存儲的本地數據所有丟失了!那麼很遺憾,你的用戶羣體可能已經流 失一大半了。

聽起來好像挺恐怖的樣子,難道說在產品發佈出去以後還不能升級數據庫了?固然不 是,其實只須要進行一些合理的控制,就能夠保證在升級數據庫的時候數據並不會丟失了。 下面咱們就來學習一下如何實現這樣的功能,你已經知道,每個數據庫版本都會對應 一個版本號,當指定的數據庫版本號大於當前數據庫版本號的時候,就會進入到 onUpgrade() 方法中去執行更新操做。這裏須要爲每個版本號賦予它各自改變的內容,而後在onUpgrade()方法中對當前數據庫的版本號進行判斷,再執行相應的改變就能夠了。 接着就讓咱們來模擬一個數據庫升級的案例,仍是由 MyDatabaseHelper 類來對數據庫進行管理。初版的程序要求很是簡單,只須要建立一張 Book 表,MyDatabaseHelper 中的 代碼以下所示:

 

public class MyDatabaseHelper extends SQLiteOpenHelper {

 

 public static final String CREATE_BOOK = "create table Book ("

+ "id integer primary key autoincrement, "

+ "author text, "

+ "price real, "

+ "pages integer, "

+ "name text)";

 

public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) {

super(context, name, factory, version);

}

 

@Override

 

public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_BOOK);

}

 

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

 

}

不過,幾星期以後又有了新需求,此次須要向數據庫中再添加一張 Category 表。因而, 修改 MyDatabaseHelper 中的代碼,以下所示:

 

public class MyDatabaseHelper extends SQLiteOpenHelper {

 

 

public static final String CREATE_BOOK = "create table Book ("

+ "id integer primary key autoincrement, "

+ "author text, "

+ "price real, "

+ "pages integer, "

+ "name text)";

 

 

public static final String CREATE_CATEGORY = "create table Category ("

+ "id integer primary key autoincrement, "

+ "category_name text, "

+ "category_code integer)";

 

 

public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) {

super(context, name, factory, version);

}

 

 

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_BOOK);

db.execSQL(CREATE_CATEGORY);

}

 

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

switch (oldVersion) {

case 1:

db.execSQL(CREATE_CATEGORY);

default:

}

}

 

}

能夠看到,在 onCreate()方法裏咱們新增了一條建表語句,而後又在 onUpgrade()方法中 添加了一個 switch 判斷,若是用戶當前數據庫的版本號是 1,就只會建立一張 Category 表。 這樣當用戶是直接安裝的第二版的程序時,就會將兩張表一塊兒建立。而當用戶是使用第二版 的程序覆蓋安裝初版的程序時,就會進入到升級數據庫的操做中,此時因爲 Book 表已經 存在了,所以只須要建立一張 Category 表便可。

可是沒過多久,新的需求又來了,此次要給 Book 表和 Category 表之間創建關聯,須要 在 Book 表中添加一個 category_id 的字段。再次修改 MyDatabaseHelper 中的代碼,以下所示:

 

public class MyDatabaseHelper extends SQLiteOpenHelper {

 

public static final String CREATE_BOOK = "create table Book ("

+ "id integer primary key autoincrement, "

+ "author text, "

+ "price real, "

+ "pages integer, "

+ "name text, "

+ "category_id integer)";

 

 

public static final String CREATE_CATEGORY = "create table Category ("

+ "id integer primary key autoincrement, "

+ "category_name text, "

+ "category_code integer)";

 

 

public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) {

super(context, name, factory, version);

}

 

 

@Override

public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_BOOK); db.execSQL(CREATE_CATEGORY);


 

 

}

 

 

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

switch (oldVersion) {

case 1:

db.execSQL(CREATE_CATEGORY);

case 2:

db.execSQL("alter table Book add column category_id integer");

default:

}

}

 

 

}

能夠看到,首先咱們在 Book 表的建表語句中添加了一個 category_id 列,這樣當用戶直 接安裝第三版的程序時,這個新增的列就已經自動添加成功了。然而,若是用戶以前已經安 裝了某一版本的程序,如今須要覆蓋安裝,就會進入到升級數據庫的操做中。在 onUpgrade() 方法裏,咱們添加了一個新的 case,若是當前數據庫的版本號是 2,就會執行 alter 命令來爲 Book 表新增一個 category_id 列。

這裏請注意一個很是重要的細節,switch 中每個 case 的最後都是沒有使用 break 的, 爲何要這麼作呢?這是爲了保證在跨版本升級的時候,每一次的數據庫修改都能被所有執 行到。好比用戶當前是從第二版程序升級到第三版程序的,那麼 case 2 中的邏輯就會執行。 而若是用戶是直接從初版程序升級到第三版程序的,那麼 case 1 和 case 2 中的邏輯都會執 行。使用這種方式來維護數據庫的升級,無論版本怎樣更新,均可以保證數據庫的表結構是 最新的,並且表中的數據也徹底不會丟失了。

相關文章
相關標籤/搜索