前言
http://blog.devtang.com/blog/2012/04/22/use-fmdb/
sqlite (http://www.sqlite.org/docs.html) 是一个轻量级的关系数据库。iOS SDK很早就支持了sqlite,在使用时,只需要加入 libsqlite3.dylib 依赖以及引入 sqlite3.h 头文件即可。但是,原生的sqlite API在使用上相当不友好,在使用时,非常不便。于是,开源社区中就出现了一系列将sqlite API进行封装的库,而FMDB (https://github.com/ccgus/fmdb) 则是开源社区中的优秀者。
FMDB在使用上相当方便。以下是一个简单的例子:
1@H_301_19@
2@H_301_19@
3@H_301_19@
4@H_301_19@
5@H_301_19@
6@H_301_19@
7@H_301_19@
8@H_301_19@
9@H_301_19@
10@H_301_19@
11@H_301_19@
|
NSString@H_301_19@*@H_301_19@ docsdir@H_301_19@ =@H_301_19@ [@H_301_19@NSSearchPathForDirectoriesInDomains@H_301_19@(@H_301_19@ NSDocumentDirectory@H_301_19@,@H_301_19@ NSUserDomainMask@H_301_19@,@H_301_19@ YES@H_301_19@)@H_301_19@ lastObject@H_301_19@];@H_301_19@ @H_301_19@NSString@H_301_19@*@H_301_19@ dbpath@H_301_19@ =@H_301_19@ [@H_301_19@docsdir@H_301_19@ stringByAppendingPathComponent:@H_301_19@@"user.sqlite"@H_301_19@];@H_301_19@ @H_301_19@FMDatabase@H_301_19@*@H_301_19@ db@H_301_19@ =@H_301_19@ [@H_301_19@FMDatabase@H_301_19@ databaseWithPath:@H_301_19@dbpath@H_301_19@];@H_301_19@ @H_301_19@[@H_301_19@db@H_301_19@ open@H_301_19@];@H_301_19@ @H_301_19@FMResultSet@H_301_19@ *@H_301_19@rs@H_301_19@ =@H_301_19@ [@H_301_19@db@H_301_19@ executeQuery:@H_301_19@@"select * from people"@H_301_19@];@H_301_19@ @H_301_19@while@H_301_19@ ([@H_301_19@rs@H_301_19@ next@H_301_19@])@H_301_19@ {@H_301_19@ @H_301_19@ NSLog@H_301_19@(@H_301_19@@"%@ %@"@H_301_19@,@H_301_19@ @H_301_19@ [@H_301_19@rs@H_301_19@ stringForColumn:@H_301_19@@"firstname"@H_301_19@],@H_301_19@ @H_301_19@ [@H_301_19@rs@H_301_19@ stringForColumn:@H_301_19@@"lastname"@H_301_19@]);@H_301_19@ @H_301_19@}@H_301_19@ @H_301_19@[@H_301_19@db@H_301_19@ close@H_301_19@];@H_301_19@ @H_301_19@ |
可以看到,使用FMDB后的数据库代码清晰明了,比原生的API优雅多了。另外,FMDB同时兼容ARC和非ARC工程,会自动根据工程配置来调整相关的内存管理代码。
使用说明
该使用说明主要翻译自fmdb的github项目说明文档:https://github.com/ccgus/fmdb
引入相关文件
首先将FMDB从github上clone下来,然后将以下文件copy到你的工程中:
1@H_301_19@
2@H_301_19@
3@H_301_19@
4@H_301_19@
5@H_301_19@
6@H_301_19@
7@H_301_19@
8@H_301_19@
9@H_301_19@
10@H_301_19@
|
FMDatabase@H_301_19@.@H_301_19@h@H_301_19@ @H_301_19@FMDatabase@H_301_19@.@H_301_19@m@H_301_19@ @H_301_19@FMDatabaseAdditions@H_301_19@.@H_301_19@h@H_301_19@ @H_301_19@FMDatabaseAdditions@H_301_19@.@H_301_19@m@H_301_19@ @H_301_19@FMDatabasePool@H_301_19@.@H_301_19@h@H_301_19@ @H_301_19@FMDatabasePool@H_301_19@.@H_301_19@m@H_301_19@ @H_301_19@FMDatabaseQueue@H_301_19@.@H_301_19@h@H_301_19@ @H_301_19@FMDatabaseQueue@H_301_19@.@H_301_19@m@H_301_19@ @H_301_19@FMResultSet@H_301_19@.@H_301_19@h@H_301_19@ @H_301_19@FMResultSet@H_301_19@.@H_301_19@m@H_301_19@ @H_301_19@ |
建立数据库
建立数据库只需要如下一行即可,当该文件不存在时,fmdb会自己创建一个。如果你传入的参数是空串:@”” ,则fmdb会在临时文件目录下创建这个数据库,如果你传入的参数是 NULL,则它会建立一个在内存中的数据库。
1@H_301_19@
|
FMDatabase@H_301_19@ *@H_301_19@db@H_301_19@ =@H_301_19@ [@H_301_19@FMDatabase@H_301_19@ databaseWithPath:@H_301_19@@"/tmp/tmp.db"@H_301_19@];@H_301_19@ |
打开数据库
使用如下语句,如果打开失败,可能是权限不足或者资源不足。通常打开完操作操作后,需要调用close方法来关闭数据库。
1@H_301_19@
2@H_301_19@
3@H_301_19@
4@H_301_19@
5@H_301_19@
6@H_301_19@
7@H_301_19@
8@H_301_19@
|
if@H_301_19@ (@H_301_19@!@H_301_19@[@H_301_19@db@H_301_19@ open@H_301_19@])@H_301_19@ {@H_301_19@ @H_301_19@ // error @H_301_19@ @H_301_19@ return@H_301_19@;@H_301_19@ @H_301_19@}@H_301_19@ @H_301_19@// some operation@H_301_19@ @H_301_19@// ...@H_301_19@ @H_301_19@ @H_301_19@[@H_301_19@db@H_301_19@ close@H_301_19@];@H_301_19@ @H_301_19@ |
执行更新操作
除了Select操作之外,其它的都是更新操作。更新操作使用如下方法,如果有错误,可以用error参数中获得。
1@H_301_19@
|
-@H_301_19@[@H_301_19@FMDatabase@H_301_19@ executeUpdate:error:withArgumentsInArray:orVAList:@H_301_19@]@H_301_19@ |
执行查询操作
查询操作示例如下。注意:即使操作结果只有一行,也需要先调用FMResultSet的next方法。
1@H_301_19@
2@H_301_19@
3@H_301_19@
4@H_301_19@
5@H_301_19@
6@H_301_19@
7@H_301_19@
8@H_301_19@
9@H_301_19@
|
FMResultSet@H_301_19@ *@H_301_19@s@H_301_19@ =@H_301_19@ [@H_301_19@db@H_301_19@ executeQuery:@H_301_19@@"SELECT * FROM myTable"@H_301_19@];@H_301_19@ @H_301_19@while@H_301_19@ ([@H_301_19@s@H_301_19@ next@H_301_19@])@H_301_19@ {@H_301_19@ @H_301_19@ //retrieve values for each record@H_301_19@ @H_301_19@}@H_301_19@ @H_301_19@ @H_301_19@FMResultSet@H_301_19@ *@H_301_19@s@H_301_19@ =@H_301_19@ [@H_301_19@db@H_301_19@ executeQuery:@H_301_19@@"SELECT COUNT(*) FROM myTable"@H_301_19@];@H_301_19@ @H_301_19@if@H_301_19@ ([@H_301_19@s@H_301_19@ next@H_301_19@])@H_301_19@ {@H_301_19@ @H_301_19@ int@H_301_19@ totalCount@H_301_19@ =@H_301_19@ [@H_301_19@s@H_301_19@ intForColumnIndex:@H_301_19@0@H_301_19@];@H_301_19@ @H_301_19@}@H_301_19@ @H_301_19@ |
1@H_301_19@
2@H_301_19@
3@H_301_19@
4@H_301_19@
5@H_301_19@
6@H_301_19@
7@H_301_19@
8@H_301_19@
9@H_301_19@
10@H_301_19@
11@H_301_19@
|
intForColumn:@H_301_19@ @H_301_19@longForColumn:@H_301_19@ @H_301_19@longLongIntForColumn:@H_301_19@ @H_301_19@boolForColumn:@H_301_19@ @H_301_19@doubleForColumn:@H_301_19@ @H_301_19@stringForColumn:@H_301_19@ @H_301_19@dateForColumn:@H_301_19@ @H_301_19@dataForColumn:@H_301_19@ @H_301_19@datanoCopyForColumn:@H_301_19@ @H_301_19@UTF8StringForColumnIndex:@H_301_19@ @H_301_19@objectForColumn:@H_301_19@ @H_301_19@ |
通常情况下,你并不需要关闭FMResultSet,因为相关的数据库关闭时,FMResultSet也会被自动关闭。
数据参数
通常情况下,你可以按照标准的sql语句,用?表示执行语句的参数,如:
1@H_301_19@
|
INSERT@H_301_19@ INTO@H_301_19@ myTable@H_301_19@ VALUES@H_301_19@ (@H_301_19@?@H_301_19@,@H_301_19@ ?@H_301_19@,@H_301_19@ ?@H_301_19@)@H_301_19@ |
然后,可以我们可以调用executeUpdate方法来将?所指代的具体参数传入,通常是用变长参数来传递进去的,如下:
1@H_301_19@
2@H_301_19@
|
NSString@H_301_19@ *@H_301_19@sql@H_301_19@ =@H_301_19@ @"insert into User (name,password) values (?,?)"@H_301_19@;@H_301_19@ @H_301_19@[@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@sql@H_301_19@,@H_301_19@ user@H_301_19@.@H_301_19@name@H_301_19@,@H_301_19@ user@H_301_19@.@H_301_19@password@H_301_19@];@H_301_19@ @H_301_19@ |
这里需要注意的是,参数必须是NSObject的子类,所以象int,double,bool这种基本类型,需要封装成对应的包装类才行,如下所示:
1@H_301_19@
2@H_301_19@
3@H_301_19@
4@H_301_19@
|
// 错误,42不能作为参数@H_301_19@ @H_301_19@[@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@@"INSERT INTO myTable VALUES (?)"@H_301_19@,@H_301_19@ 42@H_301_19@];@H_301_19@ @H_301_19@// 正确,将42封装成 NSNumber 类@H_301_19@ @H_301_19@[@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@@"INSERT INTO myTable VALUES (?)"@H_301_19@,@H_301_19@ [@H_301_19@NSNumber@H_301_19@ numberWithInt:@H_301_19@42@H_301_19@]];@H_301_19@ @H_301_19@ |
线程安全
如果我们的app需要多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安全了。切记不能在多个线程中共同一个FMDatabase对象并且在多个线程中同时使用,这个类本身不是线程安全的,这样使用会造成数据混乱等问题。
使用FMDatabaseQueue很简单,首先用一个数据库文件地址来初使化FMDatabaseQueue,然后就可以将一个闭包(block)传入inDatabase方法中。在闭包中操作数据库,而不直接参与FMDatabase的管理。
1@H_301_19@
2@H_301_19@
3@H_301_19@
4@H_301_19@
5@H_301_19@
6@H_301_19@
7@H_301_19@
8@H_301_19@
9@H_301_19@
10@H_301_19@
11@H_301_19@
12@H_301_19@
13@H_301_19@
14@H_301_19@
15@H_301_19@
16@H_301_19@
17@H_301_19@
18@H_301_19@
19@H_301_19@
20@H_301_19@
21@H_301_19@
22@H_301_19@
23@H_301_19@
24@H_301_19@
25@H_301_19@
26@H_301_19@
27@H_301_19@
28@H_301_19@
|
// 创建,最好放在一个单例的类中@H_301_19@ @H_301_19@FMDatabaseQueue@H_301_19@ *@H_301_19@queue@H_301_19@ =@H_301_19@ [@H_301_19@FMDatabaseQueue@H_301_19@ databaseQueueWithPath:@H_301_19@aPath@H_301_19@];@H_301_19@ @H_301_19@ @H_301_19@// 使用@H_301_19@ @H_301_19@[@H_301_19@queue@H_301_19@ inDatabase:@H_301_19@^@H_301_19@(@H_301_19@FMDatabase@H_301_19@ *@H_301_19@db@H_301_19@)@H_301_19@ {@H_301_19@ @H_301_19@ [@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@@"INSERT INTO myTable VALUES (?)"@H_301_19@,@H_301_19@ [@H_301_19@NSNumber@H_301_19@ numberWithInt:@H_301_19@1@H_301_19@]];@H_301_19@ @H_301_19@ [@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@@"INSERT INTO myTable VALUES (?)"@H_301_19@,@H_301_19@ [@H_301_19@NSNumber@H_301_19@ numberWithInt:@H_301_19@2@H_301_19@]];@H_301_19@ @H_301_19@ [@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@@"INSERT INTO myTable VALUES (?)"@H_301_19@,@H_301_19@ [@H_301_19@NSNumber@H_301_19@ numberWithInt:@H_301_19@3@H_301_19@]];@H_301_19@ @H_301_19@ @H_301_19@ FMResultSet@H_301_19@ *@H_301_19@rs@H_301_19@ =@H_301_19@ [@H_301_19@db@H_301_19@ executeQuery:@H_301_19@@"select * from foo"@H_301_19@];@H_301_19@ @H_301_19@ while@H_301_19@ ([@H_301_19@rs@H_301_19@ next@H_301_19@])@H_301_19@ {@H_301_19@ @H_301_19@ // …@H_301_19@ @H_301_19@ }@H_301_19@ @H_301_19@}];@H_301_19@ @H_301_19@ @H_301_19@// 如果要支持事务@H_301_19@ @H_301_19@[@H_301_19@queue@H_301_19@ inTransaction:@H_301_19@^@H_301_19@(@H_301_19@FMDatabase@H_301_19@ *@H_301_19@db@H_301_19@,@H_301_19@ BOOL@H_301_19@ *@H_301_19@rollback@H_301_19@)@H_301_19@ {@H_301_19@ @H_301_19@ [@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@@"INSERT INTO myTable VALUES (?)"@H_301_19@,@H_301_19@ [@H_301_19@NSNumber@H_301_19@ numberWithInt:@H_301_19@3@H_301_19@]];@H_301_19@ @H_301_19@ @H_301_19@ if@H_301_19@ (@H_301_19@whoopsSomethingWrongHappened@H_301_19@)@H_301_19@ {@H_301_19@ @H_301_19@ *@H_301_19@rollback@H_301_19@ =@H_301_19@ YES@H_301_19@;@H_301_19@ @H_301_19@ return@H_301_19@;@H_301_19@ @H_301_19@ }@H_301_19@ @H_301_19@ // etc…@H_301_19@ @H_301_19@ [@H_301_19@db@H_301_19@ executeUpdate:@H_301_19@@"INSERT INTO myTable VALUES (?)"@H_301_19@,@H_301_19@ [@H_301_19@NSNumber@H_301_19@ numberWithInt:@H_301_19@4@H_301_19@]];@H_301_19@ @H_301_19@}];@H_301_19@ @H_301_19@ |
工具
为了查看sqlite中的数据,一个好的图形化界面的数据库管理程序是必不可少的。MysqL有PHPMyAdmin,那么sqlite呢?
我主要使用的是Firefox的一个名为sqlite Manager的插件,安装此插件后,可以直接打开后缀名为sqlite的数据库文件。sqlite Manager提供一个图形化的界面来执行数据查询或更改操作。如下图所示:
总结
FMDB将sqlite API进行了很友好的封装,使用上非常方便,对于那些使用纯sqlite API来进行数据库操作的app,可以考虑将其迁移到基于FMDB上,这对于以后数据库相关功能的开发维护,可以提高不少效率。
我在学习fmdb的时候做了一个小工程用于练习,我把它放到github上了。感兴趣的可以自行下载:https://github.com/tangqiaoboy/FmdbSample
祝大家玩得开心。
#import @H_301_19@<Foundation/Foundation.h>
#import @H_301_19@"FMDatabase.h"
#import @H_301_19@"FMDatabaseAdditions.h"
#import @H_301_19@"FMDatabasePool.h"
#import @H_301_19@"FMDatabaseQueue.h"
int@H_301_19@ main(int@H_301_19@ argc,const@H_301_19@ char@H_301_19@ * argv[])
{
@H_301_19@@autoreleasepool {@H_301_19@
@H_301_19@// insert code here...
@H_301_19@NSLog@H_301_19@(@H_301_19@@"Hello,World!");@H_301_19@
NSFileManager@H_301_19@ *filemgr;
NSString@H_301_19@ *currentpath;
filemgr = [[NSFileManager@H_301_19@alloc@H_301_19@] init@H_301_19@];
currentpath = [filemgrcurrentDirectoryPath@H_301_19@];
@H_301_19@//NSLog(@"%@",currentpath);
NSString@H_301_19@ *dbpath = [currentpath stringByAppendingPathComponent@H_301_19@:@"I3DB.db3"@H_301_19@];
FMDatabase@H_301_19@ *db = [FMDatabase@H_301_19@databaseWithPath@H_301_19@:dbpath];
if@H_301_19@(db == NULL@H_301_19@)
{
@H_301_19@NSLog@H_301_19@(@H_301_19@@" Create DB Failed!");@H_301_19@
}
[dbopen@H_301_19@];
@H_301_19@FMResultSet@H_301_19@ *rs = [db@H_301_19@executeQuery@H_301_19@:@H_301_19@@" select * from Tab_MHz where id = 1"];@H_301_19@
while@H_301_19@ ([rs next@H_301_19@]) {
NSLog@H_301_19@( @"%@"@H_301_19@,[rsstringForColumn@H_301_19@:@"work"@H_301_19@]);
}
[dbclose@H_301_19@];
}
}
注意数据放的位置在debug目录下