FMDB介绍
FMDB是一种第三方的开源库,FMDB就是对SQLite的API进行了封装,加上了面向对象的思想,让我们不必使用繁琐的C语言API函数,比起直接操作SQLite更加方便
FMDB优点:
- 使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
- 对比苹果自带的CoreData框架,更加轻量级和灵活
- 提供多线程安全,有效地防止数据混乱,原来的SQLite不是线程安全的
FMDB缺点:因为是OC语言封装的,失去了SQLite原来的跨平台性
使用
在 FMDB 中有三个重要的类:
FMDatabase
:是一个提供 SQLite 数据库的类,用于执行 SQL 语句。FMResultSet
:用在FMDatabase
中执行查询的结果的类。FMDatabaseQueue
:在多线程下查询和更新数据库用到的类。
数据库创建
FMDatabase
是通过一个 SQLite 数据库文件路径创建的,此路径可以是以下三者之一:
- 一个文件的系统路径。磁盘中可以不存在此文件,因为如果不存在会自动为你创建。
- 一个空的字符串
@""
。会在临时位置创建一个空的数据库,当FMDatabase
连接关闭时,该数据库会被删除。 NULL
。会在内存中创建一个数据库,当FMDatabase
连接关闭时,该数据库会被销毁。
// 创建数据库示例
FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
打开数据库
数据库必须是打开状态,才能与之交互。如果没有足够的资源和权限来打开创建数据库,数据库会打开失败。
数据库更新
SQL 语句中除过 SELECT
语句都可以称之为更新操作。包括 CREATE
,UPDATE
,INSERT
,ALTER
,COMMIT
,BEGIN
,DETACH
,DROP
,END
,EXPLAIN
,VACUUM
,REPLACE
等。一般只要不是以 SELECT
开头的 SQL 语句,都是更新语句。
执行更新语句后会返回一个 BOOL
值,返回 YES
表示执行更新语句成功,返回 NO
表示出现错误,可以通过调用 -lastErrorMessage
和 -lastErrorCode
方法获取更多错误信息。
数据库查询
通过调用 -executeQuery...
方法之一执行 SELECT
语句进行数据库查询操作。
执行查询操作后,如果成功会返回一个 FMResultSet
对象,反之会返回 nil
。通过 -lastErrorMessage
和 -lastErrorCode
方法可以确定为什么会查询失败。
为了遍历查询结果,需要 while()
循环,然后逐条记录查看。在 FMDB 中,可以通过下面的简单方式实现:
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
// 每条记录的检索值
}
即使只需要获取一个数据,也还是必须在访问查询结果前调用 -[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:
UTF8StringForColumn:
objectForColumn:
这些方法都有一个 {type}ForColumnIndex:
变体,是基于列的位置来查询数据。
通常情况下,一个 FMResultSet
没有必要手动 -close
,因为结果集合 (result set) 被释放或者源数据库关闭会自动关闭。
关闭数据库
当对数据库进行查询和更新操作完成后,需要调用 -close
关闭数据库 FMDatabase
的连接。
// 示例
[db close];
事务
FMDatabase
可以通过调用方法来开始和提交事务,也可以通过执行开始结束事务 (beginend transaction) 语句。
多语句和批处理
FMDatabase
可以通过 -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;
}];
数据处理
当给 FMDB 提供 SQL 语句时,在插入前不应该处理任何数据,而应该使用标准的 SQLite 的绑定语法。
// 示例
INSERT INTO myTable VALUES (?, ?, ?)
?
问号在 SQLite 中意为即将插入的值的占位符,FMDB 执行语句的方法都接受多个参数 (或者参数集合,比如 NSArray
,NSDictionary
,va_list
),它们都会正确转义。
也可以使用命名参数语法:
// 示例
INSERT INTO myTable VALUES (:id, :name, :value)
这些参数必须以冒号开头,SQLite 自身支持其他字符,但是命名时字典的键内部以冒号开头,就不能在你的字典的键中包含冒号。
// 示例
NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:@"My Name", @"name", nil];
[db executeUpdate:@"INSERT INTO myTable (name) VALUES (:name)" withParameterDictionary:argsDict];
因此,不应该写类似下面这行一样的错误代码:
// 错误示例
[db executeUpdate:[NSString stringWithFormat:@"INSERT INTO myTable VALUES (%@)", @"this has " lots of ' bizarre " quotes '"]];
而应该这样写:
// 正确示例
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @"this has " lots of ' bizarre " quotes '"];
所有传递给 -executeUpdate:
方法的参数都必须是对象。下面写法执行不会起作用而且会引发崩溃:
// 错误示例
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", 42];
插入一个数的正确方法是把这个数字包装成 NSNumber
对象:
// 正确示例
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]];
也可以使用 -execute*WithFormat:
这个方法将数字转换成字符串:
// 转换成字符串示例
[db executeUpdateWithFormat:@"INSERT INTO myTable VALUES (@d)", 42];
-execute*WithFormat:
这些方法后面都可以接格式字符串参数,以下 % 百分号格式符都是可以识别的:%@
, %c
, %s
, %d
, %D
, %i
, %u
, %U
, %hi
, %hu
, %qi
, %qu
, %f
, %g
, %ld
, %lu
, %lld
, %llu
。使用其他格式符可能会出现不可预知的问题。出于某种原因,可能需要在你的 SQL 语句中使用 %
字符,应该使用百分号转义一下 %%
。
#import "ViewController.h" #import "FMDB.h" @interface ViewController () { //数据库操作对象 FMDatabase *database; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; /* 1. 如果该路径下已经存在该数据库,直接获取该数据库; 2. 如果不存在就创建一个新的数据库; 3. 如果传@"",会在临时目录创建一个空的数据库,当数据库关闭时,数据库文件也被删除; 4. 如果传nil,会在内存中临时创建一个空的数据库,当数据库关闭时,数据库文件也被删除; 5.注意:表名和路径得准确哦!(路径为了避免错误,我封装起来了[self getDBPath];表名我用的UserTable) */ database = [FMDatabase databaseWithPath:[self getDBPath]];//路径 [self openTable]; //打开数据库(如果数据库不存在,则先创建,再打开) [self createTable]; //创建表 [self insertTable]; //插入数据 [self selectTable]; //查询数据 [self updataTable]; //更新数据 [self deleteTable]; //删除数据 [database close]; // 4 关闭数据库 } #pragma mark - 创建数据库路径 - (NSString *)getDBPath { NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; path = [path stringByAppendingPathComponent:@"User.db"]; NSLog(@"path:%@", path); return path; } #pragma mark == 打开数据库 -(BOOL)openTable { //打开数据库(如果数据库不存在,则先创建,再打开) BOOL ret = [database open]; if (ret) { NSLog(@"打开数据库成功!"); } else { NSLog(@"打开数据库失败!"); } return ret; } #pragma mark == 创建表 - (BOOL)createTable { // 2 创建表 //创建表的SQL语句 NSString *sql1 = @"create table if not exists UserTable(id integer primary key autoincrement, name text, age integer)"; //执行sql1这条创建表的sql语句 BOOL ret1 = [database executeUpdate:sql1]; if (ret1) { NSLog(@"创建表成功!"); } else { NSLog(@"创建表失败!"); [database close]; } return ret1; } #pragma mark == 插入数据 -(BOOL)insertTable { // 3.1 插入数据 BOOL ret2 = YES; for (int i=0; i<10; i++) { NSString *name = [NSString stringWithFormat:@"小明%d", i]; //插入的SQL语句 NSString *sql2 = @"insert into UserTable(name,age) values(?,?)"; //执行插入的SQL语句 // 插入数据必须是:NSString, NSNumber, NSData ret2 &= [database executeUpdate:sql2, name, @(12+i)]; } if (ret2) { NSLog(@"插入数据成功!"); } else { NSLog(@"插入数据失败!"); } return ret2; } #pragma mark == 查询数据 -(FMResultSet *)selectTable { // 3.2 查询数据 //查询的SQL语句 NSString *sql3 = @"select * from UserTable"; //执行查询的SQL语句 // executeQuery: 专门用于查询 // FMResultSet 是查询返回的结果集合 FMResultSet *set = [database executeQuery:sql3]; //set调用next方法就会指向下一条数据 while ( [set next] ) { //获取name的值 // NSString *name = [set stringForColumn:@"name"]; //通过属性名 NSString *name = [set stringForColumnIndex:1]; //通过属性的下标 int age = [set intForColumn:@"age"]; //通过属性名获取age NSLog(@"name:%@, age:%d", name, age); } return set; } #pragma mark == 更新数据 -(BOOL)updataTable { //3.3 更新数据 //更新数据的SQL语句 // NSString *sql4 = [NSString stringWithFormat:@"update UserTable set name=%@ where name=%@", @"'小王'", @"'小明4'"]; NSString *sql4 = @"update UserTable set name=? where name=?"; //执行更新的SQL语句 BOOL ret4 = [database executeUpdate:sql4, @"小王", @"小明5"]; NSLog(@"更新%@!", ret4 ? @"成功" : @"失败"); return ret4; } #pragma mark ==删除数据 -(BOOL)deleteTable { //3.4 删除数据 //删除数据的SQL语句 NSString *sql5 = @"delete from UserTable where name=?"; //执行删除的SQL语句 BOOL ret5 = [database executeUpdate:sql5, @"小王"]; NSLog(@"删除%@!", ret5 ? @"成功" : @"失败"); return ret5; }
FMDatabaseQueue 队列和线程安全
在多线程中同时使用 FMDatabase 单例是极其错误的想法,会导致每个线程创建一个 FMDatabase 对象。不要跨线程使用单例,也不要同时跨多线程,不然会奔溃或者异常。
因此不要实例化一个 FMDatabase 单例来跨线程使用。
相反,使用 FMDatabaseQueue,下面就是它的使用方法:
第一,创建队列。
// 创建 FMdatabaseQueue 示例
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
然后这样使用:
// 示例
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while ([rs next]) {
...
}
}];
把操作放在事务中也很简单,比如:
// 示例
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
if (whoopsSomethingWrongHappened) {
*rollback = YES;
return;
}
// ...
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];
}];
FMDatabase 将块代码 block 运行在一个串行队列上,即使在多线程同时调用 FMDatabaseQueue 的方法,它们仍然还是顺序执行。这种查询和更新方式不会影响其它,是线程安全的。
如果对你有帮助,请关注我哦!