上篇文章已經介紹瞭如何使用SharedPreferences儲存鍵值對形式的輕量級資料,對於那些相同結構的多組資料,類似於儲存Java中定義的類的多個物件屬性值,如果按照鍵值對的形式一條條讀寫,需要分別定義每條資料對應的key值,是相當繁瑣的。而如果可以使用資料庫儲存就會方便很多。

正因此,Android系統提供了對SQLite資料庫的支援,在應用中建立的資料庫,預設也是儲存在應用程式的內部儲存空間中的,這樣也只有當前應用程式內部可以訪問其資料庫中資料。

使用純粹的SQLiteDatabase類操作資料庫

在Android系統中可以使用android.database.sqlite.SQLiteDatabase資料庫類,直接操作SQLite資料庫。同時藉助android.database.sqlite.SQLiteOpenHelper資料庫幫助類,來獲取數這裡的據庫類。

定義資料庫結構類

一般要儲存的類結構與資料庫結構保持一致即可,以學生資訊為例,下面建立的Student類即可直接作為資料庫結構類,只需按照Student類中的屬性名一一對應,定義資料庫中的欄位名。

public final class Student {
private String name;
private String birthday;
private int level;
private Student() {} public static final String TABLE_NAME = "student";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_BIRTHDAY = "birthday";
public static final String COLUMN_LEVEL = "level";
}

在定義資料庫中的欄位名時,通常會定義值為 _id 的欄位作為資料表中的自增長欄位,這是為了在讀取資料表時使用android.widget.CursorAdapter介面卡子類可以正常建立其例項化物件。否則在使用CursorAdapter介面卡例項化物件時可能會丟擲java.lang.IllegalArgumentException異常。

建立資料庫

與資料庫中包含資料表的結構相對應,這裡定義繼承自SQLiteOpenHelper的子類處理資料表間關係,並在自定義子類中實現onCreate(SQLiteDatabase db)onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)方法。

通常該類的構造方法繼承自其父類SQLiteOpenHelper (Context context, String name, SQLiteDatabase.CursorFactory factory, int version),引數 context 為使用該資料庫的上下文環境;引數 name 為資料庫檔案的名字;引數 factory 為訪問資料庫使用的遊標工廠,通常傳入 null 即可;引數 version 為資料表的版本號。

該類實現的onCreate(SQLiteDatabase db)方法會在物件建立後,資料庫檔案首次建立時呼叫,因此可以在該方法中執行資料表的建立操作,引數 db 即當前資料庫物件,可呼叫該物件的相關方法操作資料庫。

onUpdate(SQLiteDatabase db, int oldVersion, int newVersion)方法會在該類物件建立後,資料庫檔案存在但資料庫版本升級時呼叫,因此在該方法中可以執行資料表的更新操作。其中引數 db 為當前資料庫物件,同樣呼叫該物件的相關方法可操作資料庫;引數 oldVersion 為資料庫版本升級之前的舊版本號;引數 newVersion 為資料庫版本升級之後的新版本號。

以上文建立Student學生類對應的資料庫為例,示例程式碼如下。當首次建立StudentDbHelper物件時,其對應資料庫版本號為10,且資料庫檔案需要首次建立,因此會執行該物件的onCreate()方法,在資料庫中執行SQL_CREATE_STUDENT定義的sql語句,建立不包含 birthday 欄位的資料表。而如果在以後需要更新資料表時,想增加 birthday 欄位,只需要在建立StudentDbHelper物件時,將其對應資料庫版本號改為20,並在onUpdate()方法中做版本號的判斷,一旦判斷符合條件,即可執行SQL_ADD_BIRTHDAY定義的sql語句。

public class StudentDbHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION_FIRST = 10;
public static final int DATABASE_VERSION_SECONDDATABASE_VERSION_SECOND = 20; public static final String DATABASE_NAME = "students.db"; private static final String SQL_CREATE_STUDENT =
"CREATE TABLE " + Student.TABLE_NAME + " (" +
Student.COLUMN_ID + " INTEGER PRIMARY KEY," +
Student.COLUMN_NAME + " TEXT," +
Student.COLUMN_LEVEL + " TEXT)"; private static final String SQL_ADD_BIRTHDAY =
"ALTER TABLE " + Student.TABLE_NAME +
" ADD COLUMN " + Student.COLUMN_BIRTHDAY + " TEXT"; public StudentDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION_FIRST);
//super(context, DATABASE_NAME, null, DATABASE_VERSION_SECOND);
} public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_STUDENT);
} public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(newVersion==DATABASE_VERSION_SECOND){
db.execSQL(SQL_DELETE_BIRTHDAY);
}
} }

操作資料庫

上邊在SQLiteDbHelper子類中已經對SQLiteDatabase類有所瞭解,除此之外,也可以在需要獲取資料庫物件的地方通過SQLiteDbHelper物件的getWritableDatabase()方法獲取可讀寫資料庫和getReadableDatabase()方法獲取可讀式資料庫。

在獲取到SQLiteDatabase資料庫物件之後,可通過其相關方法分別執行資料庫的增刪改查等操作。

呼叫該物件的insert(String table, String nullColumnHack, ContentValues values)系列方法,可以在資料庫中插入一條資料。返回 long 型別的結果表示插入資料在資料表中的id序列,如果插入失敗則返回 -1 。其中引數 table 為要插入的資料表名;引數 nullColumnHack 可以指定要插入的欄位名,通常為null時忽略,使用後邊引數中所包含的欄位資料;引數 values 指定要插入的資料,同樣使用 key-value 鍵值對的形式存取資料。

呼叫該物件的delete(String table, String whereClause, String[] whereArgs)方法,可以刪除資料庫中指定的資料。返回 int 型別的結果表示刪除的資料條數。其中引數 table 同樣為要刪除的資料表名;引數 whereClause 為指定刪除條件,其符合sql語句,但變數引數可用?代替,在後邊引數中指定具體引數值;引數 whereArgs 即為引數值陣列,長度與引數 whereClause 中的?符合數量一致。如果刪除某個資料表中的所有內容,只需將引數二和引數三均置為null即可。

呼叫該物件的update(String table, ContentValues values, String whereClause, String[] whereArgs)方法,可以更新資料庫中指定的資料。返回 int 型別的結果表示更新的資料條數。其中引數 table 同樣為要更新的資料表名;引數 values 指定要更新的欄位資料;引數 whereClause 可以指定更新條件;引數 whereArgs 對應指定更新條件中的引數值。這裡如果引數三和引數四均為null,則會更新資料表中所有資料條目。

呼叫該物件的query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)系列方法,可以查詢資料庫中的指定資料。返回android.database.Cursor型別的遊標物件,可以暫存多條結果。其中引數 table 為要查詢的資料表名;引數 columns 為返回的結果中所包含的欄位,如果為null則返回所有欄位;引數 selection 為查詢條件,同樣符合sql語句,但變數引數用?代替;引數 selectionArgs 對應於查詢條件中變數引數的值所組成的陣列;引數 groupBy 與sql語句中的 GROUP BY 一致,可以指定返回的資料中以某個欄位為分組依據,如果為null則不會分組;引數 having 同樣與sql語句中的 HAVING 一致,指定返回的資料中是否包含某個欄位,如果為null則包含所有資料;引數 orderBy 同樣與sql語句中的 ORDER BY 一致,可以指定根據某個欄位排序,如果為null則不會排序;引數 limit 與sql語句中的 LIMIT 一致,可以分頁查詢。

對於query()方法返回的Cursor型別,可以使用isX()系列方法判斷當前狀態X,使用moveX()系列方法將當前遊標移動到某條資料對應位置,使用getX()系列方法獲取遊標當前位置對應條目資料的各欄位及對應值。在使用完遊標結果之後,一定要使用close()方法關閉當前遊標,否則在下次查詢資料時將依然返回當前的遊標結果。

以上四種增刪改查對應的操作方法,都可以使用原生的sql語句實現,所以可以直接呼叫SQLiteDatabase物件的execSQL (String sql)方法,傳入一條已經定義好的sql語句即可。該方法在上文建立資料庫時已有使用示例,可支援大多數sql語句。

在資料庫中有事務的概念,也就是將多個增刪改查操作線性執行看成一個整體的操作。同樣在Android中,通過呼叫SQLiteDatabase物件的begainTransaction()方法可以啟動一次事務,在所有事務操作執行結束後,再呼叫setTransactionSuccessful()方法標誌當前事務操作以完成,如果不呼叫該方法,當前事務即便被提交也不會執行。最終再呼叫endTransaction()方法以結束當前事務,並判斷在呼叫上述setTransactionsuccessful()標誌方法條件下提交併執行當前事務。

釋放資料庫資源

在對資料庫的所有操作結束之後,一般是在使用SQLiteDbHelper物件所在的元件生命週期結束之前,要呼叫SQLiteDbHelper物件的close()方法,以釋放應用程式對資料庫的資源佔有。

藉助更便捷的Room框架

為了在開發中更聚焦於業務程式碼邏輯,而簡化資料庫的開關流程,像Room這種開發級框架也就應運而生了。Room框架是有官方提供的推薦框架,除此之外,還有其他開發者或組織提供的優秀框架,包括但不限於GreenDao、LitePal、OrmLite等。由於開發級框架使用便捷,雖效能各有優劣,但使用大同小異,這裡不再贅述。


簡單開發中對資料庫的使用只集中在增刪改查的簡單操作中,尤其是查詢資料往往取出結果後會做進一步的過濾處理,如果能在查詢操作時巧妙的藉助 GROUP BY 這種子語句,其效率定能更快一步,這裡多是開發人員容易忽略的一點。總之合理使用資料庫,對大量相同結構資料的儲存是很高效的,同時如果想進一步提高資料庫效能,建議多學習下sql語句相關內容。