在Android開發(fā)中,除了非常輕量級的應用,基本上都需要用到數(shù)據(jù)庫來存儲數(shù)據(jù)。
Android中自帶的數(shù)據(jù)庫系統(tǒng)是SQLite。不過SQL語句和在其他的結構化數(shù)據(jù)庫系統(tǒng)(MySQL、MSSQL之類的)中使用的并沒有太大差別。
在需要執(zhí)行SQL語句的時候,就是需要持有一個SQLiteDatabase類的引用,然后調用對應的方法。
rawQuery方法和execSQL方法用于執(zhí)行純SQL語句,前者可以返回一個指向返回數(shù)據(jù)的Cursor,而后者更主要是用于執(zhí)行無返回數(shù)據(jù)的SQL語句,是個返回值為void的方法。除了這兩方法之外,Android還封裝insert、query、delete、update等方法,以避免我們開發(fā)者需要手工拼接SQL語句。
這些方法的常用聲明如下:
Cursor rawQuery(String sql, String[] selectionArgs)
void execSQL(String sql)
long insert(String table, String nullColumnHack, ContentValues values)
Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
int delete(String table, String whereClause, String[] whereArgs)
int update(String table, ContentValues values, String whereClause, String[] whereArgs)
可以看到幾乎每一個方法里面都需要一個字符串數(shù)組類型的參數(shù)XXXArgs。
當然這個參數(shù)是可以為null的,只要selection或者whereClause里面已經是一條完整的SQL條件語句。但既然Android給咱們單獨封裝成了Args那必然是使用起來對咱們會有好處的嘛。
所以試圖考慮一下這樣的情形,現(xiàn)在有一個數(shù)據(jù)表sample,它的表頭有id(Integer)、name(Text)以及isGraduated(Integer)。
這時候如果我們要delete其中的一個條目,假設是id為233,name為testName的條目,那純粹的SQL語句就應該是:
DELETE FROM sample WHERE id=233 AND name='testName'
那如果調用delete方法而且不使用args參數(shù)的話,就是:
SQLiteDatabase db;
String table = "sample";
int id = 233;
String name = "testName";
int row = db.delete(table, "id=" + id + " AND name='" + name + "'", null);
哎,這name的等號那里還要有單引號,看上去真刺眼。
那如果使用了args參數(shù)的話,代碼又該長什么樣呢?
SQLiteDatabase db;
String table = "sample";
String[] args = new String[]{"233", "testName"};
int row = db.delete(table, "id=? AND name=?", args);
刺眼的引號被消去了,而且這個代碼的邏輯也是運行正常。
那為什么用了args之后就不需要添加單引號了呢?
為了解決這個疑惑,我先是一步步地深入SQLiteDataBase類中的用到了args參數(shù)的方法源碼,想嘗試在java源碼層面找到把whereClause和whereArgs合并的方法,從而得知實際調用的純SQL語句是怎樣的。但找了好一會兒,發(fā)現(xiàn)這兩參數(shù)并不是在java層面合并的,所以這個方向并無所得。
那接下來就嘗試谷歌搜索相關的資料,果不其然發(fā)現(xiàn)了一篇博文What’s good about selectionArgs in SQLite queries,說到了由于SQLite數(shù)據(jù)庫與其他的SQL數(shù)據(jù)庫不一樣,并不是一種強類型的數(shù)據(jù)庫,如果在where語句里面都用字符串進行比較——即無論表頭字段是什么數(shù)據(jù)類型,等號右邊都是用單引號裹住的字符串,這樣都是可以正確匹配的。因為在SQLite數(shù)據(jù)庫中,字符串類型都能無損地轉會它原先的數(shù)據(jù)類型(例子,字符串“1”可以轉回Integer型的1;同樣字符串“-1”也可以轉回Integer型的-1),在比較時,SQLite數(shù)據(jù)庫會把字符串轉為表頭項所屬的數(shù)據(jù)類型來進行比對,從而得到了正確的比對結果。
根據(jù)這個解釋,那其實上面使用了args時對應的SQL語句其實是這樣的:
DELETE FROM sample WHERE id='233' AND name='testName'
數(shù)字233也被單引號包裹成了字符串。
根據(jù)上面的學習了解,可以發(fā)現(xiàn)用args的好處有兩點是顯而易見的了:
- 不用在意表頭的數(shù)據(jù)類型來惡心地手動拼接字符串。
- 用了占位符的語句更為簡短易讀。
我覺得就這兩點好處已經足以說服我在用到的數(shù)據(jù)庫語句中都替換成使用args參數(shù)方式了。
講完用args參數(shù)的好處,我們再來講一下注意點。
- 占位符問號只可以用在等號右側,即"? = ?"這種占位符是非法的。
- args的綁定實際上就是把其中的一個個arg用單引號括住然后替換掉原語句中對應的占位符
針對第2點,我再詳細說一下。
一開始沒理解args參數(shù)的用法的時候,在需要用到SQL的IN語句時,我也是傻乎乎地:
//用法一
String whereClause = "id IN ?";
String[] whereArgs = new String[]{"(1,2,3)"};
又或者是:
//用法二
String whereClause = "id IN (?)";
String[] whereArgs = new String[]{"1,2,3"};
那這樣子發(fā)現(xiàn),咦怎么提示語法錯誤/沒匹配的查詢結果?明明數(shù)據(jù)庫里面有的???
那其實理解了第2點之后就能明白,其實這時候兩者對應的SQL語句其實是:
//用法一對應的SQL語句
... id IN '(1,2,3)'...
//用法二對應的SQL語句
... id IN ('1,2,3')...
那顯然用法一對應的SQL語句是有語法錯誤的;而用法二對應的SQL語句卻是去查找id為字符串'1,2,3'的,但在id的值都是數(shù)字的情況下自然是沒有匹配的條目返回。
但是我們的本意的SQL語句應該是:
//本意的SQL語句
... id IN (1,2,3)...
其中的1/2/3均是參數(shù),那么要用args來替代的話,其實應該是:
//用了args替換后對應的SQL語句
... id IN ('1','2','3')...
這樣就能得到準確的查詢結果。
那么根據(jù)第2點注意點,這個SQL語句要用args來綁定的話,那么這才是正確的使用方式:
//正確的用法
String whereClause = "id IN (?,?,?)";
String[] whereArgs = new String[]{"1","2","3"};
這樣用的話就會發(fā)現(xiàn)查詢的結果正常了。
以上就是我最近在寫數(shù)據(jù)庫相關邏輯時學到的內容,希望能幫到能看到本文的諸位,減少開發(fā)時可能走到的彎路,提升開發(fā)效率,謝謝。
有疑問或不同見解,歡迎回復討論。
參考文獻: