这几天在写文件下载,断点续传,需要用到FMDB进行数据的管理,等写完也总结一下。
其实也是这两天才学习 FMDB
,google了一下资料有很多,就去 github 上面先 Star 一下FMDB
,发现他README.markdown
写的很详细。然后也看了唐巧大牛的技术博客,写了FMDBDemo玩玩。
集成到项目
通过 CocoaPods 添加 pod 'FMDB'
然后 pod update --verbose --no-repo-update
OK~
创建数据库
导入 libsqlite3.tbd
(libsqlite3.0.tbd
也可以)
引入 #import "FMDB.h"
创建数据库
NSString *doc =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) lastObject];
fileName = [doc stringByAppendingPathComponent:@"downCourse.sqlite"];
DLog(@"sql文件路径 : %@",fileName);
我创建在Documents
目录下
关于创建数据库:
An
FMDatabase
is created with a path to a SQLite database file. This path can be one of these three:
- A file system path. The file does not have to exist on disk. If it does not exist, it is created for you.
- An empty string (@””). An empty database is created at a temporary location. This database is deleted with the
FMDatabase
connection is closed.
NULL
. An in-memory database is created. This database will be destroyed with the FMDatabase connection is closed.
意思就是
建立数据库只需要如下一行即可 , 当该文件不存在时,fmdb 会自己创建一个。如果你传入的参数是空串:@”” ,则 fmdb 会在临时文件目录下创建这个数据库,如果你传入的参数是 NULL,则它会建立一个在内存中的数据库。
打开数据库
FMDatabase *db = [FMDatabase databaseWithPath:fileName];
if ([db open]){
// sql
}
[db close];
就可以了 执行完 注意一定要 [db close]
关闭数据库
创建表
-(void)createTable{
// NSFileManager * fileManager = [NSFileManager defaultManager];
// if ([fileManager fileExistsAtPath:fileName] == NO) {
if (!isTableExist) {
FMDatabase *db = [FMDatabase databaseWithPath:fileName];
if ([db open]){
// 创建表
BOOL result = [db executeUpdate:@"CREATE TABLE 't_down_course' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 'courseId' INTEGER, 'downPath' VARCHAR(255),'courseInfo' text)"];
if (result){
DLog(@"创建表成功");
isTableExist = YES;
[self showAlertWithMessage:@"创建表 --- 成功" completion:nil];
}else{
[self showAlertWithMessage:@"创建表 --- 失败" completion:nil];
}
[db close];
}else{
[self showAlertWithMessage:@"打开数据库 --- 失败" completion:nil];
}
}
// }
}
创建名称为 t_down_course
的表
对表的属性怎删改
语法:
BOOL result = [db executeUpdate:sql]
-
如需在表中添加列,请使用下列语法:
ALTER TABLE table_name ADD column_name datatype
-
要删除表中的列,请使用下列语法:
ALTER TABLE table_name DROP COLUMN column_name
-
更新表中列的属性,请使用下列语法:
ALTER TABLE table_name MODIFY COLUMN column_name datatype
插入数据
-(void)addData{
FMDatabase *db = [FMDatabase databaseWithPath:fileName];
NSDictionary *dic = @{
@"code": @0,
@"msg": @"success",
@"data": @{
@"course": @{
@"id": @24,
@"crowdId": @4,
@"ccode": @"KIa8nNpVmc",
@"cname": @"健健康康",
@"coverImg": @"http://ocd2lp9uj.bkt.clouddn.com/FaceQ1445612150222.jpg",
@"description": @"淋漓尽致",
@"speaker": @2,
@"speakerName": @"刘欣成",
@"speakerHeadIcon": @"http://ocd2lp9uj.bkt.clouddn.com/FaceQ1445612150222.jpg",
@"startTime": @"2016-10-15 11:21:44",
@"endTime": @"2016-10-15 11:26:50",
@"liveStatus": @"2",
@"saveStatus": @"2"
}
}
};
NSString *json = [self dictionaryToJson:dic];
if ([db open]){
NSString *insertSql = @"insert into 't_down_course'(courseId,downPath,courseInfo) values(?,?,?)";
BOOL result = [db executeUpdate:insertSql,[NSString stringWithFormat:@"%d",count],fileName,json];
if (result){
DLog(@"添加数据成功");
[self showAlertWithMessage:@"添加数据 --- 成功" completion:nil];
}else{
DLog(@"添加数据失败");
[self showAlertWithMessage:@"添加数据 --- 失败" completion:nil];
}
count++;
[db close];
}else{
[self showAlertWithMessage:@"打开数据库 --- 失败" completion:nil];
}
}
插入数据类型必须为NSObject
的子类
基本类型需要封装为对应的包装类
支持占位符,后添加再数据
插入也支持以字典的方式
NSDictionary *arguments = @{@"identifier": @(identifier), @"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
if (!success) {
NSLog(@"error = %@", [db lastErrorMessage]);
}
支持对事务的控制
[db open]
[db beginDeferredTransaction];//开启事务
BOOL result = [db executeUpdate:insertSql,[NSString stringWithFormat:@"%d",count],fileName,json];
if(result){
[db commit];// 提交事务
}else{
[db rollback];// 回滚事务
}
查询数据
-(void)selectData{
FMDatabase *db = [FMDatabase databaseWithPath:fileName];
if ([db open]){
NSString *selectSql = @"select * from t_down_course";
FMResultSet * rs = [db executeQuery:selectSql];
while ([rs next]) {
int course = [rs intForColumn:@"id"];
NSString * courseId = [rs stringForColumn:@"courseId"];
NSString * downPath = [rs stringForColumn:@"downPath"];
NSString * courseInfo = [rs stringForColumn:@"courseInfo"];
DLog(@"course id = %d, courseId = %@, downPath = %@ courseInfo = %@", course, courseId, downPath,courseInfo);
NSMutableDictionary *dic = [self dictionaryWithJsonString:courseInfo];
DLog(@"%@",dic);
}
[db close];
}else{
[self showAlertWithMessage:@"打开数据库 --- 失败" completion:nil];
}
}
注意:即使操作结果只有一行,也需要先调用 FMResultSet 的 next 方法。
FMDB提供了多个方法获取不同类型的数据
- intForColumn:
- longForColumn:
- longLongIntForColumn:
- boolForColumn:
- doubleForColumn:
- stringForColumn:
- dateForColumn:
- dataForColumn:
- dataNoCopyForColumn:
- UTF8StringForColumnIndex:
- objectForColumn:
这些方法也都有一个{类型} ForColumnIndex:,用于检索数据基于列的位置的结果,而不是列的名称。
比如 [rs intForColumnIndex:0]
支持多个语句批处理
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;
}];
更新数据
-(void)updateData{
FMDatabase *db = [FMDatabase databaseWithPath:fileName];
if ([db open]){
BOOL result = [db executeUpdate:@"update t_down_course set downPath = '更新的数据' where id = 3"];
if (result){
DLog(@"更新数据成功");
[self showAlertWithMessage:@"更新数据 --- 成功" completion:nil];
}else{
DLog(@"更新数据失败");
[self showAlertWithMessage:@"更新数据 --- 失败" completion:nil];
}
[db close];
}else{
[self showAlertWithMessage:@"打开数据库 --- 失败" completion:nil];
}
}
删除数据
-(void)deleteData{
FMDatabase *db = [FMDatabase databaseWithPath:fileName];
if ([db open]){
BOOL result = [db executeUpdate:@"delete from t_down_course where id = 2"];
if (result){
DLog(@"删除数据成功");
[self showAlertWithMessage:@"删除数据 --- 成功" completion:nil];
}else{
DLog(@"删除数据失败");
[self showAlertWithMessage:@"删除数据 --- 失败" completion:nil];
}
[db close];
}else{
[self showAlertWithMessage:@"打开数据库 --- 失败" completion:nil];
}
}
删除表
-(void)dropTable{
FMDatabase *db = [FMDatabase databaseWithPath:fileName];
if ([db open]){
BOOL result = [db executeUpdate:@"drop table t_down_course"];
if (result){
DLog(@"删除表成功");
isTableExist = NO;
[self showAlertWithMessage:@"删除表 --- 成功" completion:nil];
}else{
DLog(@"删除表失败");
[self showAlertWithMessage:@"删除表 --- 失败" completion:nil];
}
[db close];
}else{
[self showAlertWithMessage:@"打开数据库 --- 失败" completion:nil];
}
}
多线程操作
如果应用中使用了多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安全了。 应用中不可在多个线程中共同使用一个FMDatabase对象操作数据库,这样会引起数据库数据混乱。 为了多线程操作数据库安全,FMDB使用了FMDatabaseQueue,使用FMDatabaseQueue很简单,首先用一个数据库文件地址来初使化FMDatabaseQueue,然后就可以将一个闭包(block)传入inDatabase方法中。 在闭包中操作数据库,而不直接参与FMDatabase的管理。
创建单例
//
// XCDatabaseHelper.h
// Training
//
// Created by LXC on 2016/12/3.
// Copyright © 2016年 LXC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FMDB.h"
@interface XCDatabaseHelper : NSObject
+ (XCDatabaseHelper *) sharedInstance;
- (void) inDatabase:(void(^)(FMDatabase *))block;
@end
//
// XCDatabaseHelper.m
// Training
//
// Created by LXC on 2016/12/3.
// Copyright © 2016年 LXC. All rights reserved.
//
#import "XCDatabaseHelper.h"
@implementation XCDatabaseHelper
{
FMDatabaseQueue* queue;
}
-(id)init{
self = [super init];
if(self){
NSString *doc =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) lastObject];
NSString *dbFilePath = [doc stringByAppendingPathComponent:@"downCourse.sqlite"];
queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
}
return self;
}
+ (XCDatabaseHelper *) sharedInstance{
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(void) inDatabase:(void(^)(FMDatabase*))block{
[queue inDatabase:^(FMDatabase *db){
block(db);
}];
}
@end
调用
[[XCDatabaseHelper sharedInstance] inDatabase:^(FMDatabase *db) {
BOOL result = [db executeUpdate:@"update t_down_course set downloadStatus = '1' where courseId = ?",_course.ids];
if (result){
}
}];
这里就不需要 close
注意
FMDatabaseQueue虽然看似一个队列,实际上它本身并不是,它通过内部创建一个Serial的dispatch_queue_t来处理通过inDatabase和inTransaction传入的Blocks,所以当我们在主线程(或者后台)调用inDatabase或者inTransaction时,代码实际上是同步的。FMDatabaseQueue这么设计的目的是让我们避免发生并发访问数据库的问题,因为对数据库的访问可能是随机的(在任何时候)、不同线程间(不同的网络回调等)的请求。内置一个Serial队列后,FMDatabaseQueue就变成线程安全了,所有的数据库访问都是同步执行,而且这比使用@synchronized或NSLock要高效得多。
但是这么一来就有了一个问题:如果后台在执行大量的更新,而主线程也需要访问数据库,虽然要访问的数据量很少,但是在后台执行完之前,还是会阻塞主线程。
引用这篇文章
Over~~ 洗澡睡觉😴😴😴