SQLite3以及FMDB的簡單實用

SQLite

SQLite是一款輕型的嵌入式數據庫,它占用資源非常的低,在嵌入式設備中,可能只需要幾百K的內存就夠了。同時它的處理速度也是非常快速的。

  1. 因為在iOS中SQLite3依賴于系統提供的libsqlite.tbd這個類庫,所以在使用之前首先我們需要導入這個類庫;
  2. 打開數據庫,打開數據庫成功之后進行創表操作;
 // 數據庫在沙盒中的存放路徑;
    NSString *filename = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"shops.db"];
    
    //1.0 打開數據庫;
    /* 參數一:數據庫文件存放路徑;如果數據庫文件不存在, 系統會自動創建文件并且自動初始化數據庫
       參數二:數據庫實例對象;
     */
    if (sqlite3_open(filename.UTF8String, &_db) == SQLITE_OK) {

        const char *sql = "CREATE TABLE IF NOT EXISTS t_shop (id integer PRIMARY KEY, name text NOT NULL, price real);";
        
        //2.0 執行操作;
        char *errmsg = NULL;
        /* 參數一:數據庫實例對象;
           參數二:執行的SQL語句;
         */
        
        if (sqlite3_exec(self.db, sql, NULL, NULL, &errmsg)!= SQLITE_OK) {
           
             NSLog(@"創表失敗--%s", errmsg);
        }
    } else {
        NSLog(@"打開數據庫失敗");
    }  

在上面的代碼中,我們做了兩個操作,首先打開數據庫我們使用了sqlite3_open這個方法,該方法根據文件路徑打開數據庫,如果不存在,則會創建一個新的數據庫。如果result等于常量SQLITE_OK,則表示成功打開數據庫。該方法有兩個參數,如下:

int sqlite3_open(
    const char *filename,   // 數據庫的文件路徑
    sqlite3 **ppDb          // 數據庫實例
);

其次,我們使用sqlite3_exec方法來執行建表操作。該方法適合于查詢結果集之外的任何操作。

2.執行任何SQL語句
int sqlite3_exec(
    sqlite3*,                                  // 一個打開的數據庫實例
    const char *sql,                           // 需要執行的SQL語句
    int (*callback)(void*,int,char**,char**),  // SQL語句執行完畢后的回調
    void *,                                    // 回調函數的第1個參數
    char **errmsg                              // 錯誤信息
);
  1. 執行查詢操作;
 NSString * sql=[NSString stringWithFormat:@"SELECT name,price FROM %@;",tableName];
    
    // sqlite3_stmt實例,用來獲得數據庫數據
    sqlite3_stmt *stmt = NULL;
 
    // 預準備,其實就是分析SQL語句的合法性
    if (sqlite3_prepare_v2(self.db, sql.UTF8String, -1, &stmt, NULL) == SQLITE_OK) {
        // 返回SQLITE_ROW代表成功取出一條數據
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            
            const char *name = (const char *)sqlite3_column_text(stmt, 0);
            
            const char *price = (const char *)sqlite3_column_text(stmt, 1);
        }
    }

上面用到了兩個方法:

3.檢查SQL語句的合法性(查詢前的準備)
int sqlite3_prepare_v2(
    sqlite3 *db,            // 數據庫實例
    const char *zSql,       // 需要檢查的SQL語句
    int nByte,              // SQL語句的最大字節長度,-1代表系統會幫我們自動計算
    sqlite3_stmt **ppStmt,  // sqlite3_stmt實例,用來獲得數據庫數據
    const char **pzTail
);
4.查詢一行數據
int sqlite3_step(sqlite3_stmt*); // 如果查詢到一行數據,就會返回SQLITE_ROW

查詢分三個階段:
準備階段:sqlite3_prepare_v2()
執行階段:sqlte3_step()
終止階段:sqlite3_finalize()

  1. 使用約束變量
    sqlite3_bind是用來給sqlite3_stmt *pStmt語句增加值的,對于不同類型的參數要選用不同的函數。
    例如,要執行帶兩個約束變量的插入操作,第一個變量是int類型,第二個是C字符串:
char *sql = "insert into oneTable values (?, ?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(database, sql, -1, &stmt, nil) == SQLITE_OK) {
   sqlite3_bind_int(stmt, 1, 235);
   sqlite3_bind_text(stmt, 2, "valueString", -1, NULL);
}
if (sqlite3_step(stmt) != SQLITE_DONE)
NSLog(@"Something is Wrong!");
sqlite3_finalize(stmt);

這里,sqlite3_bind_int(stmt, 1, 235);有三個參數:
第一個是sqlite3_stmt類型的變量
第二個是所約束變量的標簽index,(從1開始)
第三個參數是要加的值
有一些函數多出兩個變量,例如
sqlite3_bind_text(stmt, 2, "valueString", -1, NULL);
第四個參數代表第三個參數中需要傳遞的長度。對于C字符串來說,-1表示傳遞全部字符串。
第五個參數是一個回調函數,比如執行后做內存清除工作。

  1. 利用stmt獲得某一字段的值(字段的下標從0開始)
double sqlite3_column_double(sqlite3_stmt*, int iCol);  // 浮點數據
int sqlite3_column_int(sqlite3_stmt*, int iCol); // 整型數據
sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); // 長整型數據
const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); // 二進制文本數據
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);  // 字符串數據

因為獲取某一列的值我們不知道具體的類型,所以我們可以遍歷字段,然后根據類型自動匹配:

    
    //存放最終的返回結果;
    NSMutableArray * muArray=[NSMutableArray array];
    
    //2.0 執行預處理 獲取結果,如果下一行有記錄,就會自動返回SQLITE_ROW,同時會自動將指針移動到下一行;
    while (sqlite3_step(ppStmt) == SQLITE_ROW) {
 
        //存放當前行的數據;
        NSMutableDictionary * mnDic=[NSMutableDictionary dictionary];
        [muArray addObject:mnDic];
        
        //獲取當前行 中 列的個數;
        int columnCount= sqlite3_column_count(ppStmt);
        
        for(int i=0;i<columnCount;i++ )
        {
       
         //獲取列名稱;
         NSString * columnName=[NSString stringWithUTF8String:sqlite3_column_name(ppStmt, i)];
            
            //獲取列的類型
          int columnType= sqlite3_column_type(ppStmt, i);
            
            //匹配類型;
            id value;
            //#define SQLITE_INTEGER  1
            //#define SQLITE_FLOAT    2
            //#define SQLITE_BLOB     4
            //#define SQLITE_NULL     5
            //#define SQLITE3_TEXT    3
            switch (columnType) {
                case SQLITE_INTEGER:
                {
                    value= @(sqlite3_column_int(ppStmt, i));
                    break;
                }
                case SQLITE_FLOAT:
                {
                    value= @(sqlite3_column_double(ppStmt, i));
                    break;
                }
                case SQLITE_BLOB:
                {
                    value= CFBridgingRelease(sqlite3_column_blob(ppStmt, i));
                    break;
                }
                case SQLITE_NULL:
                {
                    value= @"";
                    break;
                }
                 case SQLITE3_TEXT:
                {
                    const char * columnValue=(const char *)sqlite3_column_text(ppStmt, i);
                    value=[NSString stringWithUTF8String:columnValue];
                    break;
                }
                
                default:
                    break;
            }
            
            [mnDic setValue:value forKeyPath:columnName];
        }
    }
    
    //3.0 關閉數據庫
    sqlite3_finalize(ppStmt);//銷毀準備語句,防止內存泄漏;
    [self closeDBWithUID:uid];
    return  muArray;

FMDB

FMDB是iOS平臺的SQLite數據庫框架,它以OC的方式封裝了SQLite的C語言API,使用起來更加面向對象,省去了很多麻煩的C語言代碼,對比蘋果自帶的Core Data框架,更加輕量級和靈活,提供了多線程安全的數據庫操作方法,有效地防止數據混亂。
FMDB有三個主要的類:
FMDatabase:一個FMDatabase對象就代表一個單獨的SQLite數據庫
FMResultSet:使用FMDatabase執行查詢后的結果集
FMDatabaseQueue:用于在多線程中執行多個查詢或更新,它是線程安全的
在FMDB中,除查詢以外的所有操作,都稱為“更新”
create、drop、insert、update、delete

更新操作
使用executeUpdate:方法執行更新:

- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments

查詢操作

- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments

例如:

// 查詢數據
FMResultSet *result = [_db executeQuery:@"SELECT * FROM t_student"];

// 遍歷結果集
while ([result next]) {
    NSString *name = [rs stringForColumn:@"name"];
    int age = [rs intForColumn:@"age"];
    double score = [rs doubleForColumn:@"score"];
}

FMDatabaseQueue
FMDatabase這個類是線程不安全的,如果在多個線程中同時使用一個FMDatabase實例,會造成數據混亂等問題,為了保證線程安全,FMDB提供了非常方便快捷的FMDatabaseQueue類:

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jim"];
    
    FMResultSet *rs = [db executeQuery:@"select * from t_student"];
    while ([rs next]) {
        // …
    }
}];

使用事物:

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jim"];
    
    FMResultSet *result = [db executeQuery:@"select * from t_student"];
    while ([result next]) {
        // …
    }
}];
#import <Foundation/Foundation.h>

@interface StatusTool : NSObject
/**
 *  根據請求參數去沙盒中加載緩存的微博數據
 *
 *  @param params 請求參數
 */
+ (NSArray *)statusesWithParams:(NSDictionary *)params;

/**
 *  存儲微博數據到沙盒中
 *
 *  @param statuses 需要存儲的微博數據
 */
+ (void)saveStatuses:(NSArray *)statuses;
@end

具體實現:

#import "StatusTool.h"
#import "FMDB.h"

@implementation StatusTool

static FMDatabase *_db;
+ (void)initialize
{
    // 1.通過指定SQLite數據庫文件路徑來創建FMDatabase對象
    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"Statuses.db"];
    _db = [FMDatabase databaseWithPath:path];
    
    //2.0 打開數據庫
    [_db open];
    
    // 3.0創表
    [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_status (id integer PRIMARY KEY, status blob NOT NULL, idstr text NOT NULL);"];
}

+ (NSArray *)statusesWithParams:(NSDictionary *)params
{
    // 根據請求參數生成對應的查詢SQL語句
    NSString *sql = nil;
    if (params[@"since_id"]) {
        sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr > %@ ORDER BY idstr DESC LIMIT 20;", params[@"since_id"]];
    } else if (params[@"max_id"]) {
        sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr <= %@ ORDER BY idstr DESC LIMIT 20;", params[@"max_id"]];
    } else {
        sql = @"SELECT * FROM t_status ORDER BY idstr DESC LIMIT 20;";
    }
    
    // 執行SQL
    FMResultSet *set = [_db executeQuery:sql];
    NSMutableArray *statuses = [NSMutableArray array];
    while (set.next) {
        //可以根據columnName來獲取數據,跟不用根據列號;
        NSData *statusData = [set objectForColumnName:@"status"];
        NSDictionary *status = [NSKeyedUnarchiver unarchiveObjectWithData:statusData];
        [statuses addObject:status];
    }
    return statuses;
}

+ (void)saveStatuses:(NSArray *)statuses
{
    // 要將一個對象存進數據庫的blob字段,最好先轉為NSData,一個對象要遵守NSCoding協議,實現協議中相應的方法,才能轉成NSData
    for (NSDictionary *status in statuses) {
        // NSDictionary --> NSData
        NSData *statusData = [NSKeyedArchiver archivedDataWithRootObject:status];
        [_db executeUpdateWithFormat:@"INSERT INTO t_status(status, idstr) VALUES (%@, %@);", statusData, status[@"idstr"]];

    }
}
@end

SQLite進行事務的SQL語句:

只要在執行SQL語句前加上以下的SQL語句,就可以使用事務功能了:
開啟事務的SQL語句,"begin transaction;"
進行提交的SQL語句,"commit transaction;"
進行回滾的SQL語句,"rollback transaction;"

只有事務提交了,開啟事務期間的操作才會生效

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容