昨天我們集中在 Non-query SQL 敘述 (insert/update/delete) 的介紹。今天我們會介紹 SQLiteDatabase [1] 針對跟查詢 (Query) 相關的 SQL 敘述所提供的 Methods,包含了 rawQuery 和 query,這兩個 Methods 都會回傳一個 Cursor 物件 [2],因此今天也會對 Cursor 做一個簡單的說明。
首先我們來看今天要討論的程式碼:
package lincyu.babylog.db;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.ArrayList;
public class BabyDB {
private static final String BABYID = "_babyid";
private static final String NAME = "_name";
private static final String NICKNAME = "_nickname";
private static final String BIRTHDAY = "_birthday";
private static final String GENDER = "_gender";
private static final String HEADSHOT = "_headshot";
static final String BABY_TABLE = "babytable";
static final String CREATE_BABY_TABLE = "create table " +
BABY_TABLE + "(" +
BABYID + " INTEGER PRIMARY KEY " +
"AUTOINCREMENT DEFAULT 1 , " +
NAME + ", " +
NICKNAME + ", " +
BIRTHDAY + ", " +
GENDER + ", " +
HEADSHOT + ");";
public synchronized static void addBaby(Context context, Baby baby) {
addBaby(context, baby.babyid, baby.name, baby.nickname, baby.headshot,
baby.birthday, baby.gender);
}
public synchronized static void addBaby(Context context, int babyid,
String name, String nickname, String headshot,
long birthday, int gender) {
DBOpenHelper dbHelper = new DBOpenHelper(context);
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(NAME, name);
cv.put(NICKNAME, nickname);
cv.put(HEADSHOT, headshot);
cv.put(BIRTHDAY, birthday);
cv.put(GENDER, gender);
if (babyid == -1) { // New Record
db.insert(BABY_TABLE, null, cv);
} else { // Existing Record
db.update(BABY_TABLE, cv, BABYID + "=" + babyid, null);
}
db.close();
}
public synchronized static void removeBaby(Context context, int babyid) {
DBOpenHelper dbHelper = new DBOpenHelper(context);
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete(BABY_TABLE, BABYID + "=" + babyid, null);
db.close();
}
public synchronized static ArrayList<Baby> getAllBabies(Context context) {
ArrayList<Baby> babylist = new ArrayList<Baby>();
DBOpenHelper dbHelper = new DBOpenHelper(context);
SQLiteDatabase db = dbHelper.getWritableDatabase();
// Cursor c = db.rawQuery("select * from " + BABY_TABLE + ";", null);
Cursor c = db.query(BABY_TABLE, null, null, null, null, null, null);
getRecordsKernel(c, babylist);
c.close();
db.close();
return babylist;
}
public synchronized static Baby getBabyById(Context context, int babyid) {
DBOpenHelper dbHelper = new DBOpenHelper(context);
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor c = db.query(BABY_TABLE, null, BABYID + "=" + babyid, null, null, null, null);
Baby baby = new Baby();
if (c.getCount() == 1) {
c.moveToFirst();
baby = db2record(c);
}
c.close();
db.close();
return baby;
}
private static void getRecordsKernel(Cursor c, ArrayList<Baby> babylist) {
c.moveToFirst();
for (int i = 0; i < c.getCount(); i++) {
Baby baby = db2record(c);
babylist.add(baby);
c.moveToNext();
}
}
private static Baby db2record(Cursor c) {
int babyid = -1;
try {
babyid = c.getInt(c.getColumnIndex(BABYID));
} catch (Exception e) {
babyid = -1;
}
String name = null;
try {
name = c.getString(c.getColumnIndex(NAME));
} catch (Exception e) {
name = "";
}
String nickname = null;
try {
nickname = c.getString(c.getColumnIndex(NICKNAME));
} catch (Exception e) {
nickname = "";
}
String headshot = null;
try {
headshot = c.getString(c.getColumnIndex(HEADSHOT));
} catch (Exception e) {
headshot = "";
}
long birthday = 0;
try {
birthday = c.getLong(c.getColumnIndex(BIRTHDAY));
} catch (Exception e) {
birthday = 0;
}
int gender = 0;
try {
gender = c.getInt(c.getColumnIndex(GENDER));
} catch (Exception e) {
gender = Baby.GENDER_UNKNOWN;
}
Baby baby = new Baby(babyid, name, nickname, headshot,
birthday, gender);
return baby;
}
}
66 ~ 79 行是對資料庫做「查詢 (Query)」的範例,69 ~ 70 行應該已經不需要說明了,簡單地說就是打開資料庫,並產生一個 SQLiteDatabase 物件,讓我們可以對資料庫進行存取。「查詢 」資料庫的方法有兩種,72 行示範了 rawQuery 的使用方法,rawQuery 的第 1 個參數是一個字串,此字串是一個 SQL 的 select 敘述,由於這系列的文章焦點不再資料庫,因此關於 select 的語法請讀者自行參考相關書籍或網路上的教學文章,至於 rawQuery 的第 2 個參數的用法跟昨天所介紹 whereArgs 是一樣的,select 敘述可搭配 where 設定查詢條件,條件可接問號「?」,這些問號的對應值就是於第 2 個參數中設定。在我們的範例中,我們沒有設定任何條件,也就是要取出所有的記錄,因此第 2 個參數直接填上 null 即可。
除了使用 rawQuery,我們亦可使用 SQLiteDatabase 這個物件的 query 方法來做查詢,query 所需要的參數比較多,query 有多重定義 (Method Overloading),程式中使用的是下面這個定義:
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
第 1 個參數是表格名稱,第 2 個參數是想要取出的 Columns,填上 null 代表我們要取出所有欄位,也就是 select *,第 3 和第 4 個參數是跟 where 子句相關的參數,我們已經看過很多次了,就不再敘述,由於我們要取出所有記錄,因此這兩個參數也都填上 null,第 5 和第 6 個參數跟 SQL 語法的 GROUP BY 和 HAVING 相關,由於沒有使用到,因此也是填上 null,最後第 7 個參數則是排序的依據,由於在程式中沒特別去做排序,因此又是 null。事實上第 72 和第 73 行的執行結果會是一樣的。
不管是 rawQuery 或 Query 都會回傳一個 Cursor 物件 [2],讀者可以把 Cursor 想成是指向查詢後新表格的游標,例如原本的表格有 5 欄 6 列,可是執行完 select 後的結果,可能只剩下 3 欄 2 列,Cursor 就是指向這個 3 欄 2 列的新表格,Cursor有許多Methods,有一些Methods是用來存取「欄(Column)」,有一些Methods是用來存取「列(Row)」,例如 getCount 是回傳符合查詢結果的「列」的數目,亦即記錄的數目,若想要得知表格有多少「欄」,則需要呼叫 getColumnCount 方法。
程式 98 ~ 105 行是利用 Cursor 來讀取查詢結果的範例,首先第 99 行呼叫 Cursor 物件的 moveToFirst 讓 Cursor 指向查詢結果的第 1 筆記錄,接著利用 for 回圈一筆一筆的讀取,其中第 103 行的 moveToNext 會讓 Cursor 移動到下一筆記錄,而真正把記錄的值讀出來是寫在 107 ~ 149 行的 db2record,Cursor 類別的 getInt, getLong, getString, getFloat 等 Methods 能讓讀出不同資料型態的值,然而它們所需要的參數都是 columnindex,而我們有的是 Column Name,不過只要利用 Cursor 物件的 getColumnIndex 就能把 Column Name 轉成 Column Index。db2record 會回傳一個 Baby 物件,最後程式第 102 行把這個 Baby 物件加入一個 Baby 動態陣列 (ArrayList)。
相信經過這幾天的介紹,讀者對於 Android 中資料庫的存取方法應該已經有初步的了解,更多關於 Android 資料庫存取的議題可參考讀者的書籍 [3]。
參考資料
[1] SQLiteDatabase | Android Developers, http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
[2] Cursor | Android Developers, http://developer.android.com/reference/android/database/Cursor.html
[3] 林致宇, Android程式設計入門與應用(附範例光碟), 全華出版社, ISBN: 9789572194126, http://www.opentech.com.tw/search/bookinfo.asp?isbn=9789572194126&companyID=04383129