IOS数据存储之FMDB数据库

前言:

    最近几天一直在折腾数据库存储,之前文章(http://www.cnblogs.com/whoislcj/p/5485959.html)介绍了Sqlite 数据库,SQLite是一种小型的轻量级的关系型数据库,不过直接用ios sdk提供的API来进行数据库开发,多多少少感觉不那么得心应手。后来也学了更加面向对象的CoreData数据库,不过coreData感觉对数据库的支持不太那么好,虽然操作方便,但是损失了性能以及效率,对于数据量比较大的app来说就有点不太合适了,如果有兴趣的可以看下之前有关coreData的文章(http://www.cnblogs.com/whoislcj/p/5488024.html)。今天来学习一下对于IOS数据库封装比较好的数据库第三方开源库FMDB,他对sqlite sdk API进行了二次封装,直接使用OC来访问,让使用变得更方便,更熟悉。

FMDB 源码托管地址:

   https://github.com/ccgus/fmdb

FMDB使用常用类:

   FMDatabase : 一个单一的SQLite数据库,用于执行SQL语句。
   FMResultSet :执行查询一个FMDatabase结果集,这个和Android的Cursor类似。
   FMDatabaseQueue :在多个线程来执行查询和更新时会使用这个类。

作为一名实用主义开发者的我一般希望大家能够把我的代码复制到项目稍作修改就能使用,秉着这个理念,为此我特意写了一个FMDB数据库管理类FMDBManager:数据库的创建,打开,关闭,升级,数据的增删改查,以及事务的开启和开启事务的好处。


接下来看下
FMDBManager具体代码实现:

FMDBManager.h

#import <Foundation/Foundation.h>

@interface FMDBManager : NSObject<NSCopying>

//创建数据库管理者单例
+(instancetype)shareManager;

//创建数据库
-(void)createDb;

//打开数据库
-(BOOL)openDb;

//关闭数据库
-(BOOL)closeDb;

//创建数据库表
-(void)creatTable;

//插入数据
-(void)insertData:(NSString*)tempName;

//插入数据未开启事务
-(void)insertDataByNomarl:(NSArray*)tempNames;

//插入数据开启事务
-(void)insertDataByTransaction:(NSArray*)tempNames;

//删除数据
-(void)deleteData:(NSString*)tempName;

//删除数据
-(void)deleteData;

//修改数据
-(void)updateData:(NSString*)tempName;

//查询数据
-(void)queryData;

@end

FMDBManager.m

#import "FMDBManager.h"
#import "FMDatabase.h"

#define DBNAME @"fbdb_test"
#define TBNAME @"persons" //表名
#define DBVERSION 1      //数据库版本
#define DBVERSIONKEY @"dbversion_key" //存储数据库版本key

static FMDBManager *shareManager=nil;

@implementation FMDBManager
{
    FMDatabase *db;
}

-(instancetype)init
{
    self=[super init];
    if (self) {
        [self createDb];
        [self creatTable];
        [self upgrade];
    }
    return self;
}
//创建数据库管理者单例
+(instancetype)shareManager
{
    if(shareManager==nil){
        @synchronized(self){
            if(shareManager==nil){
                shareManager =[[[self class]alloc]init];
            }
        }
    }
    return shareManager;
}

-(id)copyWithZone:(NSZone *)zone
{
    
    return shareManager;
}

+(id)allocWithZone:(struct _NSZone *)zone
{
    if(shareManager==nil){
        shareManager =[super allocWithZone:zone];
    }
    return shareManager;
}

//检查数据库是否需要升级
- (void)upgrade {
    //获取存储好的原版本号
    NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY];
    if (DBVERSION <= oldVersionNum || oldVersionNum == 0) {
        return;
    }
    
    //升级
    [self upgrade:oldVersionNum];
    
    // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
    [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
}

//根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
    //对比数据库版本
    if (oldVersion >= DBVERSION) {
        return;
    }
    switch (oldVersion) {
        case 0:
            //执行相应的升级操作
            break;
        case 1:
            //执行相应的升级操作
            break;
        case 2:
            //执行相应的升级操作
            break;
        default:
            break;
    }
    oldVersion ++;
    // 递归判断是否需要升级
    [self upgrade:oldVersion];
}

//创建数据库
-(void)createDb
{
    NSString *doc=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName=[doc stringByAppendingPathComponent:DBNAME];
    db = [FMDatabase databaseWithPath:fileName];
}

//打开数据库
-(BOOL)openDb
{
    return [db open];
}

//关闭数据库
-(BOOL)closeDb
{
    return  [db close];
}

//创建数据库表
-(void)creatTable
{
    [self openDb];
    NSString *creatTableSql=[NSString stringWithFormat:@"create table if not exists %@(id integer primary key,name text)",TBNAME];
    
    BOOL result=[db executeUpdate:creatTableSql];
    if(result){
        NSLog(@"创建表成功");
    }else{
        NSLog(@"创建表失败");
    }
    [self closeDb];
}

//插入数据
-(void)insertData:(NSString*)tempName
{
    [self openDb];
    NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,tempName];
    BOOL result=[db executeUpdate:insertSql];
    if(result){
        NSLog(@"插入数据成功");
    }else{
        NSLog(@"插入数据失败");
    }
    [self closeDb];
}

//插入数据未开启事务
-(void)insertDataByNomarl:(NSArray*)tempNames;
{
    [self openDb];
    for(NSString *name in tempNames){
        NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
        BOOL result=[db executeUpdate:insertSql];
        if(result){
            //NSLog(@"插入数据成功");
        }else{
           // NSLog(@"插入数据失败");
        }
    }
    [self closeDb];
}

//插入数据开启事务
-(void)insertDataByTransaction:(NSArray*)tempNames{
    [self openDb];
    [db beginTransaction];
    BOOL isRollBack = NO;
    @try {
        for(NSString *name in tempNames){
            NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
            BOOL result=[db executeUpdate:insertSql];
            if(result){
                //NSLog(@"插入数据成功");
            }else{
               // NSLog(@"插入数据失败");
            }
        }
    }
    @catch (NSException *exception) {
        isRollBack = YES;
        [db rollback];
    }
    @finally {
        if (!isRollBack) {
            [db commit];
        }
    }
    [self closeDb];
}

//删除数据
-(void)deleteData:(NSString*)tempName
{
    [self openDb];
    NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ where name = '%@'",TBNAME,tempName];
    BOOL result=[db executeUpdate:deleteSql];
    if(result){
        NSLog(@"删除数据成功");
    }else{
        NSLog(@"删除数据失败");
    }
    [self closeDb];
}

//删除数据
-(void)deleteData
{
    [self openDb];
    NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ ",TBNAME];
    BOOL result=[db executeUpdate:deleteSql];
    if(result){
        NSLog(@"删除数据成功");
    }else{
        NSLog(@"删除数据失败");
    }
    [self closeDb];
}

//修改数据
-(void)updateData:(NSString*)tempName
{
    [self openDb];
    NSString *updateSql=[NSString stringWithFormat:@"update %@ set name ='test' where name = '%@'",TBNAME,tempName];
    BOOL result=[db executeUpdate:updateSql];
    if(result){
        NSLog(@"更新数据成功");
    }else{
        NSLog(@"更新数据失败");
    }
    [self closeDb];
}

//查询数据
-(void)queryData
{
    [self openDb];
    NSString *querySql =[NSString stringWithFormat:@"select * from %@",TBNAME];
    FMResultSet *resultSet = [db executeQuery:querySql];
    
    // 2.遍历结果
    while ([resultSet next]) {
        int ID = [resultSet intForColumn:@"id"];
        NSString *name = [resultSet stringForColumn:@"name"];
        NSLog(@"Id = %d name= %@ ",ID,name);
    }
    
    [self closeDb];
}

以上是具体实现,接下来看下怎么使用:

             //插入100条数据
              for(int i=0;i<10;i++){
                  NSString *string = [[NSString alloc] initWithFormat:@"%d",i];
                  [[FMDBManager shareManager]insertData:string];
              }
              //然后查询一下
              [[FMDBManager shareManager]queryData];
              //然后删除一条数据
              [[FMDBManager shareManager]deleteData:@"5"];
              //更新数据
              [[FMDBManager shareManager]updateData:@"3"];
              //然后查询一下
              [[FMDBManager shareManager]queryData];
              //删除数据
              [[FMDBManager shareManager]deleteData];
              //然后查询一下
              [[FMDBManager shareManager]queryData];

看了调用方式 是不是觉得很简单,瞬间有没有感觉源码写代码竟然可以如此惬意!当然注重代码质量的我们应该更见注重代码的执行效率,就像一家装修很豪华的饭店但是饭菜做的很难吃一样,再美好的东西也变得然并卵了,接下来我看下FMDB有没封装过导致效率下降了呢?为此我准备测试一下批量插入操作,为了对比我直接拿之前同样是10000条数据的Sqlite结果来对比。

FMDB 未开启事务

//插入数据未开启事务
-(void)insertDataByNomarl:(NSArray*)tempNames;
{
    [self openDb];
    for(NSString *name in tempNames){
        NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
        BOOL result=[db executeUpdate:insertSql];
        if(result){
            //NSLog(@"插入数据成功");
        }else{
           // NSLog(@"插入数据失败");
        }
    }
    [self closeDb];
}

FMDB 开启事务

//插入数据开启事务
-(void)insertDataByTransaction:(NSArray*)tempNames{
    [self openDb];
    [db beginTransaction];
    BOOL isRollBack = NO;
    @try {
        for(NSString *name in tempNames){
            NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
            BOOL result=[db executeUpdate:insertSql];
            if(result){
                //NSLog(@"插入数据成功");
            }else{
               // NSLog(@"插入数据失败");
            }
        }
    }
    @catch (NSException *exception) {
        isRollBack = YES;
        [db rollback];
    }
    @finally {
        if (!isRollBack) {
            [db commit];
        }
    }
    [self closeDb];
}

测试程序

    //测试事务
    NSMutableArray *testArray =[[NSMutableArray alloc]init];
    int testMaxCount =10000;
    for(int i=0;i<testMaxCount;i++){
        NSString *string = [[NSString alloc] initWithFormat:@"%d",i];
        [testArray addObject:string];
    }
    
    //未开启事务插入
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    
    [[FMDBManager shareManager]insertDataByNomarl:testArray];
    CFAbsoluteTime end=CFAbsoluteTimeGetCurrent();
    NSLog(@"普通插入 time cost: %0.3f", end - start);
    
    //删除数据
    [[FMDBManager shareManager]deleteData];
    
    //开启事务插入
    start = CFAbsoluteTimeGetCurrent();
    
    [[FMDBManager shareManager]insertDataByTransaction:testArray];
    
    end=CFAbsoluteTimeGetCurrent();
    NSLog(@"开启事务插入 time cost: %0.3f", end - start);

运行结果:测试数据 10000条 执行时间单位 秒

   开启事务:0.050

为开启事务:5.058 

 

那么之前的sqlite呢?

开启事务耗时:0.049

未开启事务耗时:5.614

哈哈,上面的对比结果一目了然,FMDB既保证了执行的效率,又方便的开发,真是IOS开发工程师的一大利器。

 

对比了效率,问题来了:对于数据库升级支持的怎么样呢?

其实这个很简单,让人出乎意料又在情理之中,那是因为它的升级和sqlite方案一样一样的!小二!直接上代码!

检查升级:

-(instancetype)init
{
    self=[super init];
    if (self) {
        [self createDb];
        [self creatTable];
        [self upgrade];//检查升级
    }
    return self;
}

具体升级逻辑:

//检查数据库是否需要升级
- (void)upgrade {
    //获取存储好的原版本号
    NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY];
    if (DBVERSION <= oldVersionNum || oldVersionNum == 0) {
        return;
    }
    
    //升级
    [self upgrade:oldVersionNum];
    
    // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
    [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
}

//根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
    //对比数据库版本
    if (oldVersion >= DBVERSION) {
        return;
    }
    switch (oldVersion) {
        case 0:
            //执行相应的升级操作
            break;
        case 1:
            //执行相应的升级操作
            break;
        case 2:
            //执行相应的升级操作
            break;
        default:
            break;
    }
    oldVersion ++;
    // 递归判断是否需要升级
    [self upgrade:oldVersion];
}

加班通宵之后的又一个晚上我写下了上面的测试程序,为了美好明天!拼了!能够时刻保持着一种学习者的心态,也希望我的技能提升的同时也能帮助一部分博友!

FMDB的使用就介绍到此~

 

posted on 2016-05-14 12:52  总李写代码  阅读(2337)  评论(0编辑  收藏  举报