從0開始弄一個面向OC數據庫(二)--插入、更新、查詢數據

前言

首先,在上一篇文章從0開始弄一個面向OC數據庫(一),咱們實現了數據庫的建立、打開、關閉、經過runtime獲取模型全部成員變量建表功能。本次代碼解決了一個上個版本代碼存在的bug,當傳targetId爲nil的時候,數據庫語句執行失敗的問題, 其次,本篇文章要實現的功能有:git

  • 傳入模型,實現數據庫的插入數據操做
  • 使用sql的API實現查詢,在外層封裝一個方法,傳入模型類型便可查詢數據庫內該表下全部的數據,並封裝成數組(NSArray <Model * > *)返回。
  • 修改數據庫表內已存在的數據,在實現這個方法時,咱們將建立並打開數據庫、建立表格,插入數據、更新數據封裝成一個方法,咱們最終將這個方法整合的邏輯爲:
  • 一、判斷數據庫內是否存在對應的表,沒有則建立
  • 二、建立表時會執行打開數據庫,若是沒有數據庫則建立再打開
  • 三、判斷對應的表內是否存在主鍵相同的記錄,存在則對該記錄進行更新,不存在則進行插入操做

這個方法的最終形態:若是是一個從零開始的狀態,沒有任何數據庫,這個方法會替咱們 建立對應的數據庫,建立對應的表格,插入對應的數據,若是存在對應的數據庫、表、數據,則進行更新github

隔斷.jpg

功能實現

一、插入數據操做

插入數據的sql語句能夠分析爲: insert into 表名(字段1名稱,字段2名稱,字段3名稱) values ('值1','值2','值3') 思路: 1.判斷數據庫內是否有對應表格,沒有則建立(這一步先不作,由於咱們目前尚未實現查詢語句暫時沒法判斷,咱們先在外面建立一個對應的表格,再執行插入操做) 2.獲取模型的全部成員變量名稱,經過KVC獲取成員變量對應的值。 3.拼接sql插入語句並執行語句 貼上咱們的代碼:sql

#pragma mark 插入數據
+ (BOOL)insertModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId {
    // 獲取表名
    Class cls = [model class];
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    // 1.判斷數據庫內是否有對應表格,沒有則建立(這一步先不作,由於咱們目前尚未實現查詢語句,咱們先在外面建立一個表格,再執行插入操做)
    // 2.插入數據
    // 獲取類的全部成員變量的名稱與類型
    NSDictionary *nameTypeDict = [CWModelTool classIvarNameAndTypeDic:cls];
    // 獲取全部成員變量的名稱,也就是sql語句字段名稱
    NSArray *allIvarNames = nameTypeDict.allKeys;
    // 獲取全部成員變量對應的值
    NSMutableArray *allIvarValues = [NSMutableArray array];
    for (NSString *ivarName in allIvarNames) {
        // 獲取對應的值,暫時不考慮自定義模型和oc模型的狀況
        id value = [model valueForKeyPath:ivarName];
        [allIvarValues addObject:value];
    }
    // insert into 表名(字段1,字段2,字段3) values ('值1''值2''值3')
    NSString *sql = [NSString stringWithFormat:@"insert into %@(%@) values('%@')",tableName,[allIvarNames componentsJoinedByString:@","],[allIvarValues componentsJoinedByString:@"','"]];
    
    return [CWDatabase execSQL:sql uid:uid];
}
複製代碼

對這個方法進行單元測試:數據庫

- (void)testInsertModel {
    // 建立表格
    BOOL result = [CWSqliteModelTool createSQLTable:[Student class] uid:@"Chavez" targetId:nil];
    XCTAssertTrue(result);
    
    Student *stu = [[Student alloc] init];
    stu.stuId = 10086;
    stu.name = @"Alibaba";
    stu.age = 16;
    stu.height = 165;
    // 插入數據
    BOOL result1 = [CWSqliteModelTool insertModel:stu uid:@"Chavez" targetId:nil];
    XCTAssertTrue(result1);
    
    Student *stu1 = [[Student alloc] init];
    stu1.stuId = 10010;
    stu1.name = @"Tencent";
    stu1.age = 17;
    stu1.height = 182;
    // 插入數據
    BOOL result2 = [CWSqliteModelTool insertModel:stu1 uid:@"Chavez" targetId:nil];
    XCTAssertTrue(result2);
    
    Student *stu2 = [[Student alloc] init];
    stu2.stuId = 10000;
    stu2.name = @"Baidu";
    stu2.age = 18;
    stu2.height = 180;
    // 插入數據
    BOOL result3 = [CWSqliteModelTool insertModel:stu2 uid:@"Chavez" targetId:nil];
    XCTAssertTrue(result3);
}
複製代碼

建立對應的數據庫以及表格,向數據庫插入3條數據,最終咱們看到測試成功,並打開對應的數據庫表格進行驗證獲得下圖,成功!數組

插入數據成功.png

二、數據庫查詢

面向sql的API咱們能夠分爲兩類,一類爲查詢操做,一類爲非查詢操做,也就是執行語句,執行語句在以前已經實現了,如今咱們來實現查詢操做,首先提供思路:安全

  • 1.打開數據庫
  • 2.預執行sql語句
  • 3.綁定數據
  • 4.執行遍歷查詢(在這裏咱們會將每一條數據封裝成字典{字段類型key : 字段值value ...},而後加入數組中保存返回)
  • 5.重置
  • 6.釋放資源,關閉數據庫

sql3爲查詢提供瞭如下兩個方法bash

// 預執行sql語句、準備語句
SQLITE_API int sqlite3_prepare_v2(
  sqlite3 *db,            /* 數據庫的操做句柄 */
  const char *zSql,       /* sql語句 */
  int nByte,              /* 參數2sql語句取出多少字節的長度。-1爲自動計算 找到\0結束符*/
  sqlite3_stmt **ppStmt,  /* 伴隨指針的地址 */
  const char **pzTail     /* 參數2減去參數3剩餘的sql語句*/
);

// 執行伴隨指針,若是結果爲SQLITE_ROW,表示還有下一條數據,伴隨指針指向下一條數據,不然結束循環
SQLITE_API int sqlite3_step(sqlite3_stmt*);
複製代碼

在CWDatabase封裝一個方法,執行查詢操做多線程

+ (NSMutableArray <NSMutableDictionary *>*)querySql:(NSString *)sql uid:(NSString *)uid {
    // 一、打開數據庫
    if (![self openDB:uid]) {
        return nil;
    }
    // 二、預執行語句
    sqlite3_stmt *ppStmt     = 0x00; //伴隨指針
    if (sqlite3_prepare_v2(cw_database, sql.UTF8String, -1, &ppStmt, nil) != SQLITE_OK) {
        NSLog(@"查詢準備語句編譯失敗");
        return nil;
    }
    // 三、綁定數據,由於咱們的sql語句中不帶有?用來賦值,因此不須要進行綁定
    // 四、執行遍歷查詢
    NSMutableArray *rowDicArray = [NSMutableArray array];
    while (sqlite3_step(ppStmt) == SQLITE_ROW) { // SQLITE_ROW表示還有下一條數據
        // 獲取有多少列(也就是一條數據有多少個字段)
        int columnCount = sqlite3_column_count(ppStmt);
        // 存儲一條數據的全部字段名與值 的字典
        NSMutableDictionary *rowDict = [NSMutableDictionary dictionary];
        // 遍歷數據庫一條數據全部字段
        for (int i = 0; i < columnCount; i++) {
            // 獲取字段名
            NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name(ppStmt, i)];
            // 獲取字段名對應的類型
            int type = sqlite3_column_type(ppStmt, i);
            // 獲取對應的值
            id value = nil;
            switch (type) {
                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:
                    value = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(ppStmt, i)];
                    break;
                    
                default:
                    break;
            }
            [rowDict setValue:value forKey:columnName];
        }
        [rowDicArray addObject:rowDict];
    }
    // 五、重製(省略)
    // 六、釋放資源,關閉數據庫
    sqlite3_finalize(ppStmt);
    [self closeDB];
    
    return rowDicArray;
}
複製代碼

作完了面向sql的查詢以後,咱們要將這個方法封裝一下,面向模型。咱們在CWSqliteModelTool封裝一個方法函數

#pragma mark 查詢數據
// 查詢表內全部數據
+ (NSArray *)queryAllModels:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId {
    
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    
    NSString *sql = [NSString stringWithFormat:@"select * from %@", tableName];
    
    NSArray <NSDictionary *>*results = [CWDatabase querySql:sql uid:uid];
    return [self parseResults:results withClass:cls];
}

// 解析數據
+ (NSArray *)parseResults:(NSArray <NSDictionary *>*)results withClass:(Class)cls  {
    
    NSMutableArray *models = [NSMutableArray array];
    for (NSDictionary *dict in results) {
        id model = [[cls alloc] init];
        // dict類型爲{字段類型 : 字段值} 遍歷爲模型賦值
        [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            id value = obj;
            [model setValue:value forKeyPath:key];
        }];
        
        [models addObject:model];
    }
    return models;
}
複製代碼

而後咱們測試查詢函數post

- (void)testQueryModels {
    NSArray *models = [CWSqliteModelTool queryAllModels:[Student class] uid:@"Chavez" targetId:nil];
    NSLog(@"query models : %@",models);
    XCTAssertNotNil(models);
}
複製代碼

目前Chavez數據庫的Student表一共存在3條咱們剛剛插入進去的數據,執行查詢的結果若是與數據庫內的數據對應,則表明成功,最終咱們獲得以下打印:

// 數據裏有3個模型,每一個模型對應的成員變量以及值都對應上了,打印出來的不是Student模型的地址是由於咱們重寫了Student模型的description函數
2017-12-10 09:37:22.016582+0800 CWDB[2638:355147] query models : (
    " stuId = 10000 , name = Baidu , age = 18 , height = 180 ,score = 0.000000",
    " stuId = 10010 , name = Tencent , age = 17 , height = 182 ,score = 0.000000",
    " stuId = 10086 , name = Alibaba , age = 16 , height = 165 ,score = 0.000000"
)
複製代碼

測試結果表示咱們的方法徹底OK!

三、更新數據

在文章的開頭,咱們已經爲這個方法作了不少的介紹了,咱們要把建立數據庫、建立表格、插入數據、更新數據都整合在這個方法內,先說一下實現的步驟:

  • 1.獲取表名,判斷數據庫是否存在對應的表,不存在則建立
  • 2.根據主鍵,判斷數據庫內是否存在記錄
  • 3.若是數據庫內不存在記錄,進行數據插入操做
  • 4.若是數據庫內存在記錄,進行數據更新操做 首先咱們來解決步驟1的問題,如何判斷數據庫內是否存在對應的表:咱們要引入一個概念**sqlite的系統表sqlite_master,**每個 SQLite 數據庫都有一個叫 sqlite_master 的表, 它定義數據庫的模式。這個表裏面存儲着當前數據庫中全部表的相關信息,好比表的名稱、用於建立此表的sql語句、索引、索引所屬的表、建立索引的sql語句等,因此咱們只要對sqlite_master執行查詢操做便可,在CWSqliteTableTool內封裝一個方法:
+ (BOOL)isTableExists:(NSString *)tableName uid:(NSString *)uid{
    // 去sqlite_master這個表裏面去查詢建立此索引的sql語句
    NSString *queryCreateSqlStr = [NSString stringWithFormat:@"select sql from sqlite_master where type = 'table' and name = '%@'",tableName];
    
    NSMutableArray *resultArray = [CWDatabase querySql:queryCreateSqlStr uid:uid];
    return resultArray.count > 0;
}
複製代碼

最後,貼上咱們整合出來的方法:

#pragma mark 插入或者更新數據
+ (BOOL)insertOrUpdateModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId {
    // 獲取表名
    Class cls = [model class];
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    
    // 判斷數據庫是否存在對應的表,不存在則建立
    if (![CWSqliteTableTool isTableExists:tableName uid:uid]) {
        [self createSQLTable:cls uid:uid targetId:targetId];
    }
    
    // 根據主鍵,判斷數據庫內是否存在記錄
    // 判斷對象是否返回主鍵信息
    if (![cls respondsToSelector:@selector(primaryKey)]) {
        NSLog(@"若是想要操做這個模型,必需要實現+ (NSString *)primaryKey;這個方法,來告訴我主鍵信息");
        return NO;
    }
    // 獲取主鍵
    NSString *primaryKey = [cls primaryKey];
    if (!primaryKey) {
        NSLog(@"你須要指定一個主鍵來建立數據庫表");
        return NO;
    }
    // 模型中的主鍵的值
    id primaryValue = [model valueForKeyPath:primaryKey];
    //  查詢語句:  NSString *checkSql = @"select * from 表名 where 主鍵 = '主鍵值' ";
    NSString * checkSql = [NSString stringWithFormat:@"select * from %@ where %@ = '%@'",tableName,primaryKey,primaryValue];
    
    // 執行查詢語句,獲取結果
    NSArray *result = [CWDatabase querySql:checkSql uid:uid];
    // 獲取類的全部成員變量的名稱與類型
    NSDictionary *nameTypeDict = [CWModelTool classIvarNameAndTypeDic:cls];
    // 獲取全部成員變量的名稱,也就是sql語句字段名稱
    NSArray *allIvarNames = nameTypeDict.allKeys;
    // 獲取全部成員變量對應的值
    NSMutableArray *allIvarValues = [NSMutableArray array];
    for (NSString *ivarName in allIvarNames) {
        // 獲取對應的值,暫時不考慮自定義模型和oc模型的狀況
        id value = [model valueForKeyPath:ivarName];
        [allIvarValues addObject:value];
    }
    // 字段1=字段1值 allIvarNames[i]=allIvarValues[I]
    NSMutableArray *ivarNameValueArray = [NSMutableArray array];
    NSInteger count = allIvarNames.count;
    for (int i = 0; i < count; i++) {
        NSString *name = allIvarNames[I];
        id value = allIvarValues[I];
        NSString *ivarNameValue = [NSString stringWithFormat:@"%@='%@'",name,value];
        [ivarNameValueArray addObject:ivarNameValue];
    }
    
    NSString *execSql = @"";
    if (result.count > 0) { // 表內存在記錄,更新
        // update 表名 set 字段1='字段1值',字段2='字段2的值'...where 主鍵 = '主鍵值'
        execSql = [NSString stringWithFormat:@"update %@ set %@ where %@ = '%@'",tableName,[ivarNameValueArray componentsJoinedByString:@","],primaryKey,primaryValue];
    }else { // 表內不存在記錄,插入
        // insert into 表名(字段1,字段2,字段3) values ('值1''值2''值3')
        execSql = [NSString stringWithFormat:@"insert into %@(%@) values('%@')",tableName,[allIvarNames componentsJoinedByString:@","],[allIvarValues componentsJoinedByString:@"','"]];
    }
    return [CWDatabase execSQL:execSql uid:uid];
}
複製代碼

而後咱們經過單元測試來測試這個方法,首先咱們測試在數據不存在的時候的插入操做:

// 向Chavez數據庫的「Student國防科技大學」表內插入兩條數據
- (void)testCreateTableAndInsertModel {
    Student *stu = [[Student alloc] init];
    stu.stuId = 110;
    stu.name = @"中國公安";
    stu.age = 100;
    stu.height = 190;
    BOOL result = [CWSqliteModelTool insertOrUpdateModel:stu uid:@"Chavez" targetId:@"國防科技大學"];
    XCTAssertTrue(result);
    
    Student *stu1 = [[Student alloc] init];
    stu1.stuId = 119;
    stu1.name = @"中國火警";
    stu1.age = 101;
    stu1.height = 200;
    BOOL result1 = [CWSqliteModelTool insertOrUpdateModel:stu1 uid:@"Chavez" targetId:@"國防科技大學"];
    XCTAssertTrue(result1);
}
複製代碼

咱們打開數據庫軟件,按command+R刷新軟件,查看結果以下

image.png
插入數據測試成功,而後咱們再爲這個方法來寫一個更新數據的單元測試:

// 咱們把數據庫裏面stuId爲110的中國公安的數據修改,仍是調用一樣的方法
- (void)testUpdateModel {
    
    Student *stu = [[Student alloc] init];
    stu.stuId = 110;
    stu.name = @"中國公安警察支隊";
    stu.age = 90;
    stu.height = 189;
    
    BOOL result = [CWSqliteModelTool insertOrUpdateModel:stu uid:@"Chavez" targetId:@"國防科技大學"];
    XCTAssertTrue(result);
}
複製代碼

運行以後將數據庫軟件進行刷新,獲得以下表格,發現stuId爲110的數據已經按照咱們最新的模型進行了修改,測試經過。

image.png

最終,咱們將數據庫的建立、打開、建表、插入、更新操做都封裝成了一個方法,很是符合咱們以前要求的,簡單、簡單、簡單無腦。

若是你想要保存一條數據到本地數據庫,只須要執行這個方法:

+ (BOOL)insertOrUpdateModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId;
複製代碼

若是你想要更新本地數據庫的一條數據,也只須要執行上面的方法,絲絕不須要關心數據庫是如何建立的,數據庫表是如何建立的等。。


若是你想要查詢數據庫某個表裏面的全部數據,只須要執行這個方法:

+ (NSArray *)queryAllModels:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId;
複製代碼

後期咱們會爲查詢增長一些條件,來知足更多的查詢場景。

4.本篇結束

在此,數據庫的增刪查改咱們經過封裝出來的兩個方法,便捷的實現了其中3項,一些拓展的功能選擇在後面再完善吧。 在下一篇文章,咱們會實現數據庫的刪除、數據庫遷移,以及再完善一下細節,在更後面的文章,咱們會實現模型嵌套對象,數組、字典潛逃對象的狀況以及多線程安全的處理。。

github地址 本次的代碼,tag爲1.1.0,你能夠在release下找到對應的tag下載下來

最後以爲有用的同窗,但願能給本文點個喜歡,給github點個star以資鼓勵,謝謝你們。

PS: 由於我也是一邊封裝,一邊寫文章。效率可能比較低,問題也會有,歡迎你們向我拋issue,有更好的思路也歡迎你們留言!

最後再爲你們提供上一篇文章的地址。從0開始弄一個面向OC數據庫(一)

相關文章
相關標籤/搜索