IOS資料儲存之Sqlite資料庫

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

前言:

  之前學習了資料儲存的NSUserDefaults,歸檔和解檔,沙盒檔案儲存,但是對於資料量比較大,需要頻繁查詢,刪除,更新等操作的時候無論從效率上還是效能上,上述三種明顯不能滿足我們的日常開發需要了。這個時候我們必須藉助資料庫,做為Android開發的都知道採用的是一種輕量級資料庫Sqlite。其實它廣泛用於包括瀏覽器、IOS,Android以及一些便攜需求的小型web應用系統。它具備佔用資源低,處理速度快等優點。接下來我們具體認識一下。

  我們在專案開發中需要引入libsqlite3.dylib,那麼sqllite有哪些具體方法呢?

sqlite3  *db, 資料庫控制程式碼,跟檔案控制程式碼FILE很類似
sqlite3_stmt      *stmt, 這個相當於ODBC的Command物件,用於儲存編譯好的SQL語句
sqlite3_open(),   開啟資料庫,沒有資料庫時建立。
sqlite3_exec(),   執行非查詢的sql語句
Sqlite3_step(), 在呼叫sqlite3_prepare後,使用這個函式在記錄集中移動。
Sqlite3_close(), 關閉資料庫檔案還有一系列的函式,用於從記錄集欄位中獲取資料,如
sqlite3_column_text(), 取text型別的資料。
sqlite3_column_blob(),取blob型別的資料
sqlite3_column_int(), 取int型別的資料

 為了系統而且方面的學習sqlite 整理一個sqlite管理類DBManager,實現功能具體涵蓋了:資料庫的建立,開啟,關閉,升級,資料的增刪改查,以及事務的開啟和開啟事務的好處。

 DBManager.h

#import <Foundation/Foundation.h>

@interface DBManager : NSObject<NSCopying>

//建立資料庫管理者單例
+(instancetype)shareManager;

//開啟資料庫
-(void)openDb;

//關閉資料庫
-(void)closeDb;

//執行sql語句
-(void)execSql:(NSString *)sql;

//建立資料庫表
-(void)creatTable;

//刪除表結構
-(void)dropTable;

//插入資料
-(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
View Code

 

 DBManager.m

#import "DBManager.h"
#import <sqlite3.h>

#define DBNAME @"myDb" //資料庫名字
#define TBNAME @"persons" //表名
#define DBVERSION 1      //資料庫版本
#define DBVERSIONKEY @"dbversion_key" //儲存資料庫版本key

static DBManager *instance=nil;

@implementation DBManager
{
    //建立資料庫例項
    sqlite3 *db;
}


-(instancetype)init
{
    self=[super init];
    if (self) {
        [self creatTable];
        [self upgrade];
    }
    return self;
}

//建立資料庫管理者單例
+(instancetype)shareManager
{
    if(instance==nil){
        @synchronized(self){
            if(instance==nil){
                instance =[[[self class]alloc]init];
            }
        }
    }
    return instance;
}

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

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

//開啟資料庫
-(void)openDb
{
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documents = [paths objectAtIndex:0];
    NSString *database_path = [documents stringByAppendingPathComponent:DBNAME];
    
    if (sqlite3_open([database_path UTF8String], &db) == SQLITE_OK) {
       NSLog(@"資料庫開啟成功");
    }else{
        [self closeDb];
        NSLog(@"資料庫開啟失敗");
    }
    
}

//關閉資料庫
-(void)closeDb
{
    sqlite3_close(db);
}

//檢查資料庫是否需要升級
- (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];
}


//執行sql語句
-(void)execSql:(NSString *)sql
{
    [self openDb];
    char *err;
    if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
        NSLog(@"資料庫運算元據成功!");
    }else{
         sqlite3_free(err);
         NSLog(@"資料庫運算元據失敗!");
    }
    sqlite3_close(db);
}


//建立資料庫表
-(void)creatTable
{
    NSString *creatTableSql=[NSString stringWithFormat:@"create table if not exists %@(person_id integer primary key,name text)",TBNAME];
    [self execSql:creatTableSql];
}


//刪除資料庫表
-(void)dropTable
{
    NSString *dropTableSql=[NSString stringWithFormat:@"drop table  %@",TBNAME];
    [self execSql:dropTableSql];
}

//插入資料
-(void)insertData:(NSString*)tempName
{
    NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,tempName];
    [self execSql:insertSql];
}

//插入資料未開啟事務
-(void)insertDataByNomarl:(NSArray*)tempNames
{
     [self openDb];
      for(NSString *name in tempNames){
        NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
        char *err;
        if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
            NSLog(@"資料庫運算元據成功!");
        }else{
            sqlite3_free(err);
            NSLog(@"資料庫運算元據失敗!");
        }
    }
    [self closeDb];
}

//插入資料開啟事務
-(void)insertDataByTransaction:(NSArray*)tempNames
{
    @try{
        char *errorMsg;
        [self openDb];
        if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) {
            
            NSLog(@"啟動事務成功");
            
            sqlite3_free(errorMsg);
            
            //執行真正的操作
            for(NSString *name in tempNames){
                NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
                char *err;
                if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
                    NSLog(@"資料庫運算元據成功!");
                }else{
                    sqlite3_free(err);
                    NSLog(@"資料庫運算元據失敗!");
                }
            }
            
            if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) {
                
                NSLog(@"提交事務成功");
            }
            
            sqlite3_free(errorMsg);
            
        }else{
            sqlite3_free(errorMsg);
        }
        
    }
    
    @catch(NSException *e){
        
        char *errorMsg;
        if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {
            NSLog(@"回滾事務成功");
        }
        sqlite3_free(errorMsg);
        
    }
    [self closeDb];
}

//刪除資料
-(void)deleteData:(NSString*)tempName
{
    NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ where name = '%@'",TBNAME,tempName];
    [self execSql:deleteSql];
    
}

//刪除資料
-(void)deleteData
{
    NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ ",TBNAME];
    [self execSql:deleteSql];
}

//修改資料
-(void)updateData:(NSString*)tempName
{
    NSString *updateSql=[NSString stringWithFormat:@"update %@ set name ='test' where name = '%@'",TBNAME,tempName];
    [self execSql:updateSql];
    
}

//查詢資料
-(void)queryData
{
    [self openDb];
    NSString *querySql =[NSString stringWithFormat:@"select * from %@",TBNAME];
    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db, [querySql UTF8String], -1, &stmt, nil) == SQLITE_OK) {
        
        while (sqlite3_step(stmt)==SQLITE_ROW) {
            
            char *name = (char *)sqlite3_column_text(stmt, 1);
            NSString *nameString = [[NSString alloc] initWithUTF8String:name];
            NSLog(@"nameString---->%@",nameString);

        }  
        
        sqlite3_finalize(stmt);  
    }  
    [self closeDb];
    
}
@end
View Code

 

具體使用方法:

#import "DBManager.h"
#import <sqlite3.h>

#define DBNAME @"myDb" //資料庫名字
#define TBNAME @"persons" //表名
#define DBVERSION 1      //資料庫版本
#define DBVERSIONKEY @"dbversion_key" //儲存資料庫版本key

static DBManager *shareManager=nil;

@implementation DBManager
{
    //建立資料庫例項
    sqlite3 *db;
}


-(instancetype)init
{
    self=[super init];
    if (self) {
        [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)openDb
{
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documents = [paths objectAtIndex:0];
    NSString *database_path = [documents stringByAppendingPathComponent:DBNAME];
    
    if (sqlite3_open([database_path UTF8String], &db) == SQLITE_OK) {
       NSLog(@"資料庫開啟成功");
    }else{
        [self closeDb];
        NSLog(@"資料庫開啟失敗");
    }
    
}

//關閉資料庫
-(void)closeDb
{
    sqlite3_close(db);
}

//刪除資料庫
-(void)dropDb
{
    
}

//檢查資料庫是否需要升級
- (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];
}


//執行sql語句
-(void)execSql:(NSString *)sql
{
    [self openDb];
    char *err;
    if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
        NSLog(@"資料庫運算元據成功!");
    }else{
         sqlite3_free(err);
         NSLog(@"資料庫運算元據失敗!");
    }
    sqlite3_close(db);
}


//建立資料庫表
-(void)creatTable
{
    NSString *creatTableSql=[NSString stringWithFormat:@"create table if not exists %@(person_id integer primary key,name text)",TBNAME];
    [self execSql:creatTableSql];
}


//刪除資料庫表
-(void)dropTable
{
    NSString *dropTableSql=[NSString stringWithFormat:@"drop table  %@",TBNAME];
    [self execSql:dropTableSql];
}

//插入資料
-(void)insertData:(NSString*)tempName
{
    NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,tempName];
    [self execSql:insertSql];
}

//插入資料未開啟事務
-(void)insertDataByNomarl:(NSArray*)tempNames
{
     [self openDb];
      for(NSString *name in tempNames){
        NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
        char *err;
        if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
            //NSLog(@"資料庫運算元據成功!");
        }else{
            sqlite3_free(err);
            //NSLog(@"資料庫運算元據失敗!");
        }
    }
    [self closeDb];
}

//插入資料開啟事務
-(void)insertDataByTransaction:(NSArray*)tempNames
{
    @try{
        char *errorMsg;
        [self openDb];
        if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) {
            
            NSLog(@"啟動事務成功");
            
            sqlite3_free(errorMsg);
            
            //執行真正的操作
            for(NSString *name in tempNames){
                NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
                char *err;
                if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
                    //NSLog(@"資料庫運算元據成功!");
                }else{
                    sqlite3_free(err);
                   // NSLog(@"資料庫運算元據失敗!");
                }
            }
            
            if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) {
                
                NSLog(@"提交事務成功");
            }
            
            sqlite3_free(errorMsg);
            
        }else{
            sqlite3_free(errorMsg);
        }
        
    }
    
    @catch(NSException *e){
        
        char *errorMsg;
        if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {
            NSLog(@"回滾事務成功");
        }
        sqlite3_free(errorMsg);
        
    }
    [self closeDb];
}

//刪除資料
-(void)deleteData:(NSString*)tempName
{
    NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ where name = '%@'",TBNAME,tempName];
    [self execSql:deleteSql];
    
}

//刪除資料
-(void)deleteData
{
    NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ ",TBNAME];
    [self execSql:deleteSql];
}

//修改資料
-(void)updateData:(NSString*)tempName
{
    NSString *updateSql=[NSString stringWithFormat:@"update %@ set name ='test' where name = '%@'",TBNAME,tempName];
    [self execSql:updateSql];
    
}

//查詢資料
-(void)queryData
{
    [self openDb];
    NSString *querySql =[NSString stringWithFormat:@"select * from %@",TBNAME];
    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db, [querySql UTF8String], -1, &stmt, nil) == SQLITE_OK) {
        
        while (sqlite3_step(stmt)==SQLITE_ROW) {
            
            char *name = (char *)sqlite3_column_text(stmt, 1);
            NSString *nameString = [[NSString alloc] initWithUTF8String:name];
            NSLog(@"nameString---->%@",nameString);

        }  
        
        sqlite3_finalize(stmt);  
    }  
    [self closeDb];
    
}


@end
View Code

 

重點來了,曾經做個IM即時通訊方面,聊天資訊相對來說還是比較龐大一點,動不動就是成千上萬條聊天資訊,有時候執行一個訊息已讀狀態的更新都要耗時很長,那時候偶還沒有學習IOS開發,在Android平臺上我已經領略過開啟事務對效率的提升所帶來的喜悅了,那麼ios上面是怎麼開啟事務的呢?效率怎麼樣呢?讓我們一探究竟:

開啟事務:

//插入資料開啟事務
-(void)insertDataByTransaction:(NSArray*)tempNames
{
    @try{
        char *errorMsg;
        [self openDb];
        if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) {
            
            NSLog(@"啟動事務成功");
            
            sqlite3_free(errorMsg);
            
            //執行真正的操作
            for(NSString *name in tempNames){
                NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
                char *err;
                if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
                    NSLog(@"資料庫運算元據成功!");
                }else{
                    sqlite3_free(err);
                    NSLog(@"資料庫運算元據失敗!");
                }
            }
            
            if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) {
                
                NSLog(@"提交事務成功");
            }
            
            sqlite3_free(errorMsg);
            
        }else{
            sqlite3_free(errorMsg);
        }
    }
    
    @catch(NSException *e){
        
        char *errorMsg;
        if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {
            NSLog(@"回滾事務成功");
        }
        sqlite3_free(errorMsg);
    }
    [self closeDb];
}

同時準備一個未開啟事務的:

//插入資料未開啟事務
-(void)insertDataByNomarl:(NSArray*)tempNames
{
     [self openDb];
      for(NSString *name in tempNames){
        NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
        char *err;
        if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
            NSLog(@"資料庫運算元據成功!");
        }else{
            sqlite3_free(err);
            NSLog(@"資料庫運算元據失敗!");
        }
    }
    [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();

          [[DBManager shareManager]insertDataByNomarl:testArray];
          CFAbsoluteTime end=CFAbsoluteTimeGetCurrent();
          NSLog(@"普通插入 time cost: %0.3f", end - start);
          
          //刪除資料
          [[DBManager shareManager]deleteData];
          
          //開啟事務插入
           start = CFAbsoluteTimeGetCurrent();
          
          [[DBManager shareManager]insertDataByTransaction:testArray];
          
           end=CFAbsoluteTimeGetCurrent();
          NSLog(@"開啟事務插入 time cost: %0.3f", end - start);
          
          //刪除資料
          [[DBManager shareManager]deleteData];

測試結果:測試資料10000條 單位(秒)

   開啟事務耗時:0.049

未開啟事務耗時:5.614

看到上面的執行結果 是不是驚呆了。

 關於資料庫升級:由於專案業務發展,資料庫有可能要考慮到升級,比如資料庫新增表或者已有表結構變化,這時候我們就要考慮到資料升級來做版本相容:

什麼時候檢查:

-(instancetype)init
{
    self=[super init];
    if (self) {
        [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];
}

至此原生的Sqlite基礎使用就告一段落了,至於高階使用一般情況涉及到的多數是sql語句的使用,sql語句不善長的小夥伴可以去熟悉一下sql資料!這時就在想了IOS有沒有像Android一樣的第三方資料庫框架呢?也讓我等sql小白緩解一下壓力?特意查詢了一下,以下僅供參考:Sqlitepersistentobjects ,FMDB(這個在兩年前使用過)。

 

相關文章