深入浅出:SQLite(FMDB)

一直在构思这篇文章要写一些什么内容,抱着负责任的态度,去看了FMDB的文档,发现文档不是很长,并且基本把该写的东西全部都写了,于是我决定这篇文章就是把FMDB的文档翻译一遍。
喷子们看清楚了,我这里写了是把文档翻译了一遍。
(有的地方真的是只可意会啊,翻译成中文就变得怪怪的)

installed#

你可以使用CocoaPods来安装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

当然你也可以直接拖入源文件,到https://github.com/ccgus/fmdb 下载源文件,然后直接将scr->fmdb文件夹拖入到你的工程就OK。
当然你需要添加依赖库:libsqlite3.dylib
(xcode7以后你需要添加的是libsqlite3.tbd)

拖入源文件,并且添加依赖库以后你就可以使用FMDB了,当然你需要引用头文件
<code>#import "FMDB.h"</code>

ARC还是MRC#

随你便啦,FMDB很聪明的,他可以根据你的工程来执行正确的操作,所以你不用担心他会做一些不正确的事情而造成一些不好的后果。
*(不过我只在ARC下使用过,并没有真正的在MRC下验证过,不过文档中说是没有问题)

使用#

在FMDB中主要有三个类:
1、<code>FMDatabase</code> - 简单的说这个类就是代表了数据库
2、<code>FMResultSet</code> - 这个类表示查询操作的结果
3、<code>FMDatabaseQueue</code> - 多线程操作的时候你会用到这个类,并且这是线程安全,具体怎么用后面再说。

数据库的创建#

你需要使用一个path来创建一个本地FMDatabase数据库,这个path有三种类型:

1、你可以使用一个本地的地址来创建这个数据库,这个地址不一定真实存在,如果不存在,那么FMDB会创建这个数据库并返回,存在则直接返回这个数据库。
2、一个空字符串@"".FMDB会在本地创建一个临时的数据库,当数据库关闭的时候会删除这个数据库。
3、NULL.如果你将这个path填的是NULL,那么这个数据被创建在内存中,数据库关闭的时候被销毁。
(更多信息关于临时数据库和在内存中的数据库,你可以阅读这篇文档 http://www.sqlite.org/inmemorydb.html )

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

Opening#

使用数据库之前你必须打开数据库。

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

Executing Updates

所有不是select操作的操作都算update. 包括 CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE ····换句话说也就是如果你的操作不是以SELECT开头的都是update操作.

update操作返回一个布尔值,YES表示操作成功,NO表示你可以遇到了一些错误.
<code>FMDatabase</code>有两个方法 -lastErrorMessage 和 -lastErrorCode,你可以使用这两个方法来查看错误。

Executing Queries#

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

查询成功返回<code> FMResultSet</code>,失败则是返回nil.
同样你可以使用<code>FMDatabase</code>的两个方法 -lastErrorMessage and -lastErrorCode 来查找原因。

你需要用一个循环来获取到查询到的每一个值。like this:

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

不管如何你都必须使用方法 -[FMResultSet next]来获取到每一个查询结果。

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

FMResultSet 有许多类型用来返回不同类型的查询结果的值

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

以上的每个方法都有对应的 {type}ForColumnIndex: 上面那一溜方法是用例的名字来获取数据,而这个方法则是用数据在查询结果中对应的位置来获取数据

当你使用完了<code>FMResultSet</code>以后你不需要close FMResultSet因为当<code>FMDatabase</code>关闭以后<code>FMResultSet</code>也同样会关闭。

Closing#

用完了FMDB记得关。

[db close];

批处理#

<code>FMDatabase</code>的方法 executeStatements:withResultBlock:可以使用字符串来同时处理多条指令。

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;
}];

数据处理#

你必须使用标准的SQLite的标准语法,像下面那样(而不是SQL中那样):

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

‘?’这个符号表示插入数据的替代符,操作方法会接收参数来替代这个符号 (或者是代表这些参数的,比如说: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:这里需要注意的是,如果是基本数据类型比如说NSInteger,你需要转化为NSNumber,你可以使用[ NSNumber numberwithint:identifier]这样的语法或者是标识符语法:@(identifier)。
如果是插入nil,那么你不能直接插入nil,而是需要插入[NSNull null],像上面那个例子中写的是:comment ?: [NSNull null],那么如果commit是nil的话则会插入nil,反之则会插入commit.

下面这种书写方法和上面表达的是同一个意思。

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

像上面这种写法参数是以冒号开头的,SQLite支持其他字符,但是在字典中key都是以冒号为前缀的,所以你的字典key中不要包含冒号

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]);
}

最关键的一点就是:千万不要用NSString的方法比如说<code> stringWithFormat</code>来手动插入参数,必须要使用Values(?,?)这样的方法,把?当作替代符。(不要自作聪明)

FMDatabaseQueue 是线程安全的

不要在多个线程之间使用同一个<code> FMDatabase</code>对象,最好是每一个线程都有一个独立的<code> FMDatabase</code>对象,如果你在多线程之间使用同一个对象,那么会有不好的事情发生,你的app可能会经常崩溃,甚至有陨石落下来砸到你的MAC.(文档里就是这么唬人的)
如果你需要在多线程中使用<code> FMDatabase</code>,那么请使用<code> FMDatabaseQueue</code>,下面是他的使用方法:
(其实如果我们查看FMDB的源代码我们可以发现其实FMDB在后台也是使用GCD来实现的)

首先创建你的线程

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];
}];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容