上篇文章已經介紹了如何使用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語句相關內容。
