UDN-企业互联网技术人气社区

板块导航

浏览  : 300
回复  : 1

[讨论交流] 深入浅出:SQLite(FMDB)

[复制链接]
巡山霉少女的头像 楼主
发表于 2016-3-26 20:22:03 | 显示全部楼层 |阅读模式
  一直在构思这篇文章要写一些什么内容,抱着负责任的态度,去看了FMDB的文档,发现文档不是很长,并且基本把该写的东西全部都写了,于是我决定这篇文章就是把FMDB的文档翻译一遍。

  喷子们看清楚了,我这里写了是把文档翻译了一遍。

  (有的地方真的是只可意会啊,翻译成中文就变得怪怪的)

  installed

  你可以使用CocoaPods来安装FMDB

  1. pod 'FMDB'
  2. # pod 'FMDB/FTS' # FMDB with FTS
  3. # pod 'FMDB/standalone' # FMDB with latest SQLite amalgamation source
  4. # pod 'FMDB/standalone/FTS' # FMDB with latest SQLite amalgamation source and FTS
  5. # 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 )

  1. FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
复制代码


  Opening

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

  1. if (![db open]) {
  2. [db release];
  3. return;
  4. }
复制代码


  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:

  1. FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
  2. while ([s next]) {
  3. //retrieve values for each record
  4. }
复制代码


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

  1. FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
  2. if ([s next]) {
  3. int totalCount = [s intForColumnIndex:0];
  4. }
复制代码


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

  1. intForColumn:
  2. longForColumn:
  3. longLongIntForColumn:
  4. boolForColumn:
  5. doubleForColumn:
  6. stringForColumn:
  7. dateForColumn:
  8. dataForColumn:
  9. dataNoCopyForColumn:
  10. UTF8StringForColumnName:
  11. objectForColumnName:
复制代码


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

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

  Closing

  用完了FMDB记得关。 

  [db close];

  #批处理#

  1. <code>FMDatabase</code>的方法 executeStatements:withResultBlock:可以使用字符串来同时处理多条指令。
  2. NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
  3. "create table bulktest2 (id integer primary key autoincrement, y text);"
  4. "create table bulktest3 (id integer primary key autoincrement, z text);"
  5. "insert into bulktest1 (x) values ('XXX');"
  6. "insert into bulktest2 (y) values ('YYY');"
  7. "insert into bulktest3 (z) values ('ZZZ');";

  8. success = [db executeStatements:sql];

  9. sql = @"select count(*) as count from bulktest1;"
  10. "select count(*) as count from bulktest2;"
  11. "select count(*) as count from bulktest3;";

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


  数据处理

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

  1. INSERT INTO myTable VALUES (?, ?, ?, ?)
复制代码


  ‘?’这个符号表示插入数据的替代符,操作方法会接收参数来替代这个符号 (或者是代表这些参数的,比如说:NSArray, NSDictionary, va_list).

  OC中你可以像下面这样使用:

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

  5. BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", @(identifier), name, date, comment ?: [NSNull null]];
  6. if (!success) {
  7. NSLog(@"error = %@", [db lastErrorMessage]);
  8. }
复制代码


  Note:这里需要注意的是,如果是基本数据类型比如说NSInteger,你需要转化为NSNumber,你可以使用[ NSNumber numberwithint:identifier]这样的语法或者是标识符语法:@(identifier)。

  如果是插入nil,那么你不能直接插入nil,而是需要插入[NSNull null],像上面那个例子中写的是:comment ?: [NSNull null],那么如果commit是nil的话则会插入nil,反之则会插入commit.

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

  1. INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
复制代码


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

  1. NSDictionary *arguments = @{@"identifier": @(identifier), @"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
  2. BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
  3. if (!success) {
  4. NSLog(@"error = %@", [db lastErrorMessage]);
  5. }
复制代码


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

  #FMDatabaseQueue 是线程安全的

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

  首先创建你的线程

  1. FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
复制代码


  然后,像这样使用:

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

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

  7. }
  8. }];
复制代码


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

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

  5. if (whoopsSomethingWrongHappened) {
  6. *rollback = YES;
  7. return;
  8. }
  9. // etc…
  10. [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
  11. }];
复制代码


原文作者:佚名 来源:码农网
发表于 2016-3-27 09:14:25 | 显示全部楼层
赞一个
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部