FMDB详解(v2.7)
2018-04-14 本文已影响27人
开发者老岳
包含仨主要类:
FMDatabase
、FMResultSet
、FMDatabaseQueue
创建数据库
FMDatabase
是基于一个数据库文件的path创建的,该path有三种情况:
- 文件路径。文件不已经存在,若不存在会在该路径下创建一个空数据库文件。
- 空字符(
@""
)。此时会在临时目录创建一个空的数据库文件,当FMDatabase
链接关闭时自动删除。 -
NULL
。此时会在内存里创建一个空的数据库,统一链接关闭时删除。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
打开
操作数据库前,数据库必须是打开状态的。当空间不足或者没有权限的时候,就会打开失败。
if (![db open]) {
db = nil;
return;
}
Executing Updates
- 数据库语句除了
select
的都可以用update
,包括CREATE
,UPDATE
,INSERT
,ALTER
,COMMIT
,BEGIN
等。 -
Executing Updates
返回一个布尔类型值表示成功与否,返回NO
的话表示发生错误,错误原因可以从-lastErrorMessage
和-lastErrorCode
方法里查找。
Executing Queries
- 主要就是
select
的方法通过调用几个-executeQuery...
方法。查询成功返回FMResultSet
对象,失败返回nil
,失败错误原因同样可以从-lastErrorMessage
和-lastErrorCode
方法里查找。 - 若结果有多个,需要遍历结果的话,一般需要用到
while
方法,如:
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
//retrieve values for each record
}
结果只有一个的时候,也要调用next
方法。如下:
FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
int totalCount = [s intForColumnIndex:0];
}
FMResultSet
有若干方法可用于解析返回的数据,如下:
- (int)intForColumn:(NSString*)columnName;
- (long)longForColumn:(NSString*)columnName;
- (BOOL)boolForColumn:(NSString*)columnName;
- (NSString*)stringForColumn:(NSString*)columnName;
- (NSDate*)dateForColumn:(NSString*)columnName;
- (id _Nullable)objectForColumn:(NSString*)columnName;
- (const unsigned char * _Nullable)UTF8StringForColumn:(NSString*)columnName;
-
...
这些方法里都有个对应的ForColumnIndex:
方法,如下:
- (int)intForColumnIndex:(int)columnIdx;
,这些都是基于Column
在结果中的位置去查找,而不是其ColumnName
。 - 一般不用自己去
-close
一个FMResultSet
,因为在结果集(FMResultSet
)被释放或者父数据库close
时会自动close
。
Closing
当执行完queries
(查询)或updates
(更新)数据库后,要手动-close
掉FMDatabase
连接,释放相应的资源。
[db close];
Transactions
FMDatabase can begin and commit a transaction by invoking one of the appropriate methods or executing a begin/end transaction statement.
多语句和批量处理(Multiple Statements and Batch Stuff)
可以通过调用FMDatabase
的 executeStatements:withResultBlock:
方法来执行多条sql语句:
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;
}];
数据清理(Data Sanitization)
查询时问号?
的使用:
NSInteger identifier = 42;
NSDate *date = [NSDate date];
NSString *comment = nil;
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, date, comment) VALUES (?, ?, ?)", @(identifier), date, comment ?: [NSNull null]];
if (!success) {
NSLog(@"error = %@", [db lastErrorMessage]);
}
-
Note: NSInteger应该转为NSNumber对象。同样,某条为空的时候,插入数据库的是
[NSNull null]
对象,本例中comment就用了?:
方法处理的,非空就用comment
,为空就用冒号后面的[NSNull null]
。
冒号的使用:
NSDictionary *arguments = @{@"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
BOOL success = [db executeUpdate:@"INSERT INTO authors (name, date, comment) VALUES (:name, :date, :comment)" withParameterDictionary:arguments];
if (!success) {
NSLog(@"error = %@", [db lastErrorMessage]);
}
- 不要用
NSString
的stringWithFormat:
方法往sql语句里插入属性,要通过问号去插入。
FMDatabaseQueue 和线程安全
不要在多个线程里共享使用同一个FMDatabase
的单例,因为会有线程安全的问题。多线程里要用FMDatabaseQueue
,使用方法:
//创建队列
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]) {
…
}
}];
FMDataBaseQueue使用事务:
[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 ...
}];
FMDatabaseQueue
能保证在多线程里任务顺序的执行,而不会冲突。
Making custom sqlite functions, based on blocks.
You can do this! For an example, look for -makeFunctionNamed:
in main.m