YoRuo:想变得更好

爱折腾 - 乐于折腾 - 享受折腾

欢迎来到我的Blog~


FMDB 整理总结

这几天在写文件下载,断点续传,需要用到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:

  1. A file system path. The file does not have to exist on disk. If it does not exist, it is created for you.
  1. An empty string (@””). An empty database is created at a temporary location. This database is deleted with the FMDatabase connection is closed.
  1. 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]
  1. 如需在表中添加列,请使用下列语法:

     ALTER TABLE table_name ADD column_name datatype
    
  2. 要删除表中的列,请使用下列语法:

     ALTER TABLE table_name DROP COLUMN column_name
    
  3. 更新表中列的属性,请使用下列语法:

     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~~ 洗澡睡觉😴😴😴