IOS資料儲存之FMDB資料庫

總李寫程式碼發表於2016-05-14

前言:

    最近幾天一直在折騰資料庫儲存,之前文章(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的使用就介紹到此~

 

相關文章