深入淺出:SQLite(FMDB)

一直在構(gòu)思這篇文章要寫(xiě)一些什么內(nèi)容,抱著負(fù)責(zé)任的態(tài)度,去看了FMDB的文檔,發(fā)現(xiàn)文檔不是很長(zhǎng),并且基本把該寫(xiě)的東西全部都寫(xiě)了,于是我決定這篇文章就是把FMDB的文檔翻譯一遍。
噴子們看清楚了,我這里寫(xiě)了是把文檔翻譯了一遍。
(有的地方真的是只可意會(huì)啊,翻譯成中文就變得怪怪的)

installed#

你可以使用CocoaPods來(lái)安裝FMDB

pod 'FMDB'
# pod 'FMDB/FTS'   # FMDB with FTS
# pod 'FMDB/standalone'   # FMDB with latest SQLite amalgamation source
# pod 'FMDB/standalone/FTS'   # FMDB with latest SQLite amalgamation source and FTS
# pod 'FMDB/SQLCipher'   # FMDB with SQLCipher

當(dāng)然你也可以直接拖入源文件,到https://github.com/ccgus/fmdb 下載源文件,然后直接將scr->fmdb文件夾拖入到你的工程就OK。
當(dāng)然你需要添加依賴庫(kù):libsqlite3.dylib
(xcode7以后你需要添加的是libsqlite3.tbd)

拖入源文件,并且添加依賴庫(kù)以后你就可以使用FMDB了,當(dāng)然你需要引用頭文件
<code>#import "FMDB.h"</code>

ARC還是MRC#

隨你便啦,F(xiàn)MDB很聰明的,他可以根據(jù)你的工程來(lái)執(zhí)行正確的操作,所以你不用擔(dān)心他會(huì)做一些不正確的事情而造成一些不好的后果。
*(不過(guò)我只在ARC下使用過(guò),并沒(méi)有真正的在MRC下驗(yàn)證過(guò),不過(guò)文檔中說(shuō)是沒(méi)有問(wèn)題)

使用#

在FMDB中主要有三個(gè)類:
1、<code>FMDatabase</code> - 簡(jiǎn)單的說(shuō)這個(gè)類就是代表了數(shù)據(jù)庫(kù)
2、<code>FMResultSet</code> - 這個(gè)類表示查詢操作的結(jié)果
3、<code>FMDatabaseQueue</code> - 多線程操作的時(shí)候你會(huì)用到這個(gè)類,并且這是線程安全,具體怎么用后面再說(shuō)。

數(shù)據(jù)庫(kù)的創(chuàng)建#

你需要使用一個(gè)path來(lái)創(chuàng)建一個(gè)本地FMDatabase數(shù)據(jù)庫(kù),這個(gè)path有三種類型:

1、你可以使用一個(gè)本地的地址來(lái)創(chuàng)建這個(gè)數(shù)據(jù)庫(kù),這個(gè)地址不一定真實(shí)存在,如果不存在,那么FMDB會(huì)創(chuàng)建這個(gè)數(shù)據(jù)庫(kù)并返回,存在則直接返回這個(gè)數(shù)據(jù)庫(kù)。
2、一個(gè)空字符串@"".FMDB會(huì)在本地創(chuàng)建一個(gè)臨時(shí)的數(shù)據(jù)庫(kù),當(dāng)數(shù)據(jù)庫(kù)關(guān)閉的時(shí)候會(huì)刪除這個(gè)數(shù)據(jù)庫(kù)。
3、NULL.如果你將這個(gè)path填的是NULL,那么這個(gè)數(shù)據(jù)被創(chuàng)建在內(nèi)存中,數(shù)據(jù)庫(kù)關(guān)閉的時(shí)候被銷毀。
(更多信息關(guān)于臨時(shí)數(shù)據(jù)庫(kù)和在內(nèi)存中的數(shù)據(jù)庫(kù),你可以閱讀這篇文檔 http://www.sqlite.org/inmemorydb.html )

FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];

Opening#

使用數(shù)據(jù)庫(kù)之前你必須打開(kāi)數(shù)據(jù)庫(kù)。

if (![db open]) {
    [db release];
    return;
}

Executing Updates

所有不是select操作的操作都算update. 包括 CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE ····換句話說(shuō)也就是如果你的操作不是以SELECT開(kāi)頭的都是update操作.

update操作返回一個(gè)布爾值,YES表示操作成功,NO表示你可以遇到了一些錯(cuò)誤.
<code>FMDatabase</code>有兩個(gè)方法 -lastErrorMessage 和 -lastErrorCode,你可以使用這兩個(gè)方法來(lái)查看錯(cuò)誤。

Executing Queries#

SELECT 查詢使用 -executeQuery... 方法.

查詢成功返回<code> FMResultSet</code>,失敗則是返回nil.
同樣你可以使用<code>FMDatabase</code>的兩個(gè)方法 -lastErrorMessage and -lastErrorCode 來(lái)查找原因。

你需要用一個(gè)循環(huán)來(lái)獲取到查詢到的每一個(gè)值。like this:

FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
    //retrieve values for each record
}

不管如何你都必須使用方法 -[FMResultSet next]來(lái)獲取到每一個(gè)查詢結(jié)果。

FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
    int totalCount = [s intForColumnIndex:0];
}

FMResultSet 有許多類型用來(lái)返回不同類型的查詢結(jié)果的值

intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnName:
objectForColumnName:

以上的每個(gè)方法都有對(duì)應(yīng)的 {type}ForColumnIndex: 上面那一溜方法是用例的名字來(lái)獲取數(shù)據(jù),而這個(gè)方法則是用數(shù)據(jù)在查詢結(jié)果中對(duì)應(yīng)的位置來(lái)獲取數(shù)據(jù)

當(dāng)你使用完了<code>FMResultSet</code>以后你不需要close FMResultSet因?yàn)楫?dāng)<code>FMDatabase</code>關(guān)閉以后<code>FMResultSet</code>也同樣會(huì)關(guān)閉。

Closing#

用完了FMDB記得關(guān)。

[db close];

批處理#

<code>FMDatabase</code>的方法 executeStatements:withResultBlock:可以使用字符串來(lái)同時(shí)處理多條指令。

NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
                 "create table bulktest2 (id integer primary key autoincrement, y text);"
                 "create table bulktest3 (id integer primary key autoincrement, z text);"
                 "insert into bulktest1 (x) values ('XXX');"
                 "insert into bulktest2 (y) values ('YYY');"
                 "insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

sql = @"select count(*) as count from bulktest1;"
       "select count(*) as count from bulktest2;"
       "select count(*) as count from bulktest3;";

success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
    NSInteger count = [dictionary[@"count"] integerValue];
    XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
    return 0;
}];

數(shù)據(jù)處理#

你必須使用標(biāo)準(zhǔn)的SQLite的標(biāo)準(zhǔn)語(yǔ)法,像下面那樣(而不是SQL中那樣):

INSERT INTO myTable VALUES (?, ?, ?, ?)

‘?’這個(gè)符號(hào)表示插入數(shù)據(jù)的替代符,操作方法會(huì)接收參數(shù)來(lái)替代這個(gè)符號(hào) (或者是代表這些參數(shù)的,比如說(shuō):NSArray, NSDictionary, va_list).

OC中你可以像下面這樣使用:

NSInteger identifier = 42;
NSString *name = @"Liam O'Flaherty (\"the famous Irish author\")";
NSDate *date = [NSDate date];
NSString *comment = nil;

BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", @(identifier), name, date, comment ?: [NSNull null]];
if (!success) {
    NSLog(@"error = %@", [db lastErrorMessage]);
}

Note:這里需要注意的是,如果是基本數(shù)據(jù)類型比如說(shuō)NSInteger,你需要轉(zhuǎn)化為NSNumber,你可以使用[ NSNumber numberwithint:identifier]這樣的語(yǔ)法或者是標(biāo)識(shí)符語(yǔ)法:@(identifier)。
如果是插入nil,那么你不能直接插入nil,而是需要插入[NSNull null],像上面那個(gè)例子中寫(xiě)的是:comment ?: [NSNull null],那么如果commit是nil的話則會(huì)插入nil,反之則會(huì)插入commit.

下面這種書(shū)寫(xiě)方法和上面表達(dá)的是同一個(gè)意思。

INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)

像上面這種寫(xiě)法參數(shù)是以冒號(hào)開(kāi)頭的,SQLite支持其他字符,但是在字典中key都是以冒號(hào)為前綴的,所以你的字典key中不要包含冒號(hào)

NSDictionary *arguments = @{@"identifier": @(identifier), @"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
if (!success) {
    NSLog(@"error = %@", [db lastErrorMessage]);
}

最關(guān)鍵的一點(diǎn)就是:千萬(wàn)不要用NSString的方法比如說(shuō)<code> stringWithFormat</code>來(lái)手動(dòng)插入?yún)?shù),必須要使用Values(?,?)這樣的方法,把?當(dāng)作替代符。(不要自作聰明)

FMDatabaseQueue 是線程安全的

不要在多個(gè)線程之間使用同一個(gè)<code> FMDatabase</code>對(duì)象,最好是每一個(gè)線程都有一個(gè)獨(dú)立的<code> FMDatabase</code>對(duì)象,如果你在多線程之間使用同一個(gè)對(duì)象,那么會(huì)有不好的事情發(fā)生,你的app可能會(huì)經(jīng)常崩潰,甚至有隕石落下來(lái)砸到你的MAC.(文檔里就是這么唬人的)
如果你需要在多線程中使用<code> FMDatabase</code>,那么請(qǐng)使用<code> FMDatabaseQueue</code>,下面是他的使用方法:
(其實(shí)如果我們查看FMDB的源代碼我們可以發(fā)現(xiàn)其實(shí)FMDB在后臺(tái)也是使用GCD來(lái)實(shí)現(xiàn)的)

首先創(chuàng)建你的線程

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];

然后,像這樣使用:

[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    FMResultSet *rs = [db executeQuery:@"select * from foo"];
    while ([rs next]) {
        …
    }
}];

An easy way to wrap things up in a transaction can be done like this:

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    if (whoopsSomethingWrongHappened) {
        *rollback = YES;
        return;
    }
    // etc…
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
}];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容