FMDB
基本使用
相比于SQLite3来说Core Data存在着诸多优势,它面向对象,开发人员不必过多的关心更多数据库操作知识,同时它基于ObjC操作,书写更加优雅等。但是它本身也存在着一定的限制,例如如果考虑到跨平台,则只能选择SQLite,因为无论是iOS还是Android都可以使用同一个数据库,降低了开发成本和维护成本。其次是当前多数ORM框架都存在的性能问题,因为ORM最终转化为SQL操作,其中牵扯到模型数据转化,其性能自然比不上直接使用SQL操作数据库。那么有没有更好的选择呢?答案就是对SQLite进行封装。
其实通过前面对于SQLite的分析,大家应该已经看到KCDbManager就是对于SQLite封装的结果,开发人员面对的只有SQL和ObjC方法,不用过多libsqlite3的C语言API。但它毕竟只是一个简单的封装,还有更多的细节没有考虑,例如如何处理并发安全性,如何更好的处理事务等。因此,这里推荐使用第三方框架FMDB,整个框架非常轻量级但又不失灵活性,也是很多企业开发的首选。
1.FMDB既然是对于libsqlite3框架的封装,自然使用起来也是类似的,使用前也要打开一个数据库,这个数据库文件存在则直接打开否则会创建并打开。这里FMDB引入了一个MFDatabase对象来表示数据库,打开数据库和后面的数据库操作全部依赖此对象。下面是打开数据库获得MFDatabase对象的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-(void)openDb:(NSString *)dbname{
//取得数据库保存路径,通常保存沙盒Documents目录
NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@
"%@"
,directory);
NSString *filePath=[directory stringByAppendingPathComponent:dbname];
//创建FMDatabase对象
self.database=[FMDatabase databaseWithPath:filePath];
//打开数据上
if
([self.database open]) {
NSLog(@
"数据库打开成功!"
);
}
else
{
NSLog(@
"数据库打开失败!"
);
}
}
|
注意:dataWithPath中的路径参数一般会选择保存到沙箱中的Documents目录中;如果这个参数设置为nil则数据库会在内存中创建;如果设置为@””则会在沙箱中的临时目录创建,应用程序关闭则文件删除。
2.对于数据库的操作跟前面KCDbManager的封装是类似的,在FMDB中FMDatabase类提供了两个方法executeUpdate:和executeQuery:分别用于执行无返回结果的查询和有返回结果的查询。当然这两个方法有很多的重载这里就不详细解释了。唯一需要指出的是,如果调用有格式化参数的sql语句时,格式化符号使用“?”而不是“%@”、等。下面是两种情况的代码片段:
a.无返回结果
1
2
3
4
5
6
|
-(void)executeNonQuery:(NSString *)sql{
//执行更新sql语句,用于插入、修改、删除
if
(![self.database executeUpdate:sql]) {
NSLog(@
"执行SQL语句过程中发生错误!"
);
}
}
|
b.有返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-(NSArray *)executeQuery:(NSString *)sql{
NSMutableArray *array=[NSMutableArray array];
//执行查询sql语句
FMResultSet *result= [self.database executeQuery:sql];
while
(result.next) {
NSMutableDictionary *dic=[NSMutableDictionary dictionary];
for
(int i=0; i<result.columnCount; ++i) {
dic[[result columnNameForIndex:i]]=[result stringForColumnIndex:i];
}
[array addObject:dic];
}
return
array;
}
|
对于有返回结果的查询而言,查询完返回一个游标FMResultSet,通过遍历游标进行查询。而且FMDB中提供了大量intForColumn、stringForColumn等方法进行取值。
并发和事务
我们知道直接使用libsqlite3进行数据库操作其实是线程不安全的,如果遇到多个线程同时操作一个表的时候可能会发生意想不到的结果。为了解决这个问题建议在多线程中使用FMDatabaseQueue对象,相比FMDatabase而言,它是线程安全的。
创建FMDatabaseQueue的方法是类似的,调用databaseQueueWithPath:方法即可。注意这里不需要调用打开操作。
1
2
3
4
5
6
7
8
|
-(void)openDb:(NSString *)dbname{
//取得数据库保存路径,通常保存沙盒Documents目录
NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@
"%@"
,directory);
NSString *filePath=[directory stringByAppendingPathComponent:dbname];
//创建FMDatabaseQueue对象
self.database=[FMDatabaseQueue databaseQueueWithPath:filePath];
}
|
然后所有的增删改查操作调用FMDatabaseQueue的inDatabase:方法在block中执行操作sql语句即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
-(void)executeNonQuery:(NSString *)sql{
//执行更新sql语句,用于插入、修改、删除
[self.database inDatabase:^(FMDatabase *db) {
[db executeQuery:sql];
}];
}
-(NSArray *)executeQuery:(NSString *)sql{
NSMutableArray *array=[NSMutableArray array];
[self.database inDatabase:^(FMDatabase *db) {
//执行查询sql语句
FMResultSet *result= [db executeQuery:sql];
while
(result.next) {
NSMutableDictionary *dic=[NSMutableDictionary dictionary];
for
(int i=0; i<result.columnCount; ++i) {
dic[[result columnNameForIndex:i]]=[result stringForColumnIndex:i];
}
[array addObject:dic];
}
}];
return
array;
}
|
之所以将事务放到FMDB中去说并不是因为只有FMDB才支持事务,而是因为FMDB将其封装成了几个方法来调用,不用自己写对应的sql而已。其实在在使用libsqlite3操作数据库时也是原生支持事务的(因为这里的事务是基于数据库的,FMDB还是使用的SQLite数据库),只要在执行sql语句前加上“begin transaction;”执行完之后执行“commit transaction;”或者“rollback transaction;”进行提交或回滚即可。另外在Core Data中大家也可以发现,所有的增、删、改操作之后必须调用上下文的保存方法,其实本身就提供了事务的支持,只要不调用保存方法,之前所有的操作是不会提交的。在FMDB中FMDatabase有beginTransaction、commit、rollback三个方法进行开启事务、提交事务和回滚事务。