前言
首先,在上一篇文章從0開始弄一個面向OC數(shù)據(jù)庫(一),我們實現(xiàn)了數(shù)據(jù)庫的創(chuàng)建、打開、關(guān)閉、通過runtime獲取模型所有成員變量建表功能。本次代碼解決了一個上個版本代碼存在的bug,當(dāng)傳targetId為nil的時候,數(shù)據(jù)庫語句執(zhí)行失敗的問題,
其次,本篇文章要實現(xiàn)的功能有:
- 增 傳入模型,實現(xiàn)數(shù)據(jù)庫的插入數(shù)據(jù)操作
- 查 使用sql的API實現(xiàn)查詢,在外層封裝一個方法,傳入模型類型即可查詢數(shù)據(jù)庫內(nèi)該表下所有的數(shù)據(jù),并封裝成數(shù)組(NSArray <Model * > *)返回。
-
改 修改數(shù)據(jù)庫表內(nèi)已存在的數(shù)據(jù),在實現(xiàn)這個方法時,我們將創(chuàng)建并打開數(shù)據(jù)庫、創(chuàng)建表格,插入數(shù)據(jù)、更新數(shù)據(jù)封裝成一個方法,我們最終將這個方法整合的邏輯為:
1、判斷數(shù)據(jù)庫內(nèi)是否存在對應(yīng)的表,沒有則創(chuàng)建
2、創(chuàng)建表時會執(zhí)行打開數(shù)據(jù)庫,如果沒有數(shù)據(jù)庫則創(chuàng)建再打開
3、判斷對應(yīng)的表內(nèi)是否存在主鍵相同的記錄,存在則對該記錄進(jìn)行更新,不存在則進(jìn)行插入操作
這個方法的最終形態(tài):如果是一個從零開始的狀態(tài),沒有任何數(shù)據(jù)庫,這個方法會替我們 創(chuàng)建對應(yīng)的數(shù)據(jù)庫,創(chuàng)建對應(yīng)的表格,插入對應(yīng)的數(shù)據(jù),如果存在對應(yīng)的數(shù)據(jù)庫、表、數(shù)據(jù),則進(jìn)行更新

功能實現(xiàn)
1、插入數(shù)據(jù)操作
插入數(shù)據(jù)的sql語句可以分析為:
insert into 表名(字段1名稱,字段2名稱,字段3名稱) values ('值1','值2','值3')
思路:
1.判斷數(shù)據(jù)庫內(nèi)是否有對應(yīng)表格,沒有則創(chuàng)建(這一步先不做,因為我們目前還沒有實現(xiàn)查詢語句暫時無法判斷,我們先在外面創(chuàng)建一個對應(yīng)的表格,再執(zhí)行插入操作)
2.獲取模型的所有成員變量名稱,通過KVC獲取成員變量對應(yīng)的值。
3.拼接sql插入語句并執(zhí)行語句
貼上我們的代碼:
#pragma mark 插入數(shù)據(jù)
+ (BOOL)insertModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId {
// 獲取表名
Class cls = [model class];
NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
// 1.判斷數(shù)據(jù)庫內(nèi)是否有對應(yīng)表格,沒有則創(chuàng)建(這一步先不做,因為我們目前還沒有實現(xiàn)查詢語句,我們先在外面創(chuàng)建一個表格,再執(zhí)行插入操作)
// 2.插入數(shù)據(jù)
// 獲取類的所有成員變量的名稱與類型
NSDictionary *nameTypeDict = [CWModelTool classIvarNameAndTypeDic:cls];
// 獲取所有成員變量的名稱,也就是sql語句字段名稱
NSArray *allIvarNames = nameTypeDict.allKeys;
// 獲取所有成員變量對應(yīng)的值
NSMutableArray *allIvarValues = [NSMutableArray array];
for (NSString *ivarName in allIvarNames) {
// 獲取對應(yīng)的值,暫時不考慮自定義模型和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];
}
對這個方法進(jìn)行單元測試:
- (void)testInsertModel {
// 創(chuàng)建表格
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;
// 插入數(shù)據(jù)
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;
// 插入數(shù)據(jù)
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;
// 插入數(shù)據(jù)
BOOL result3 = [CWSqliteModelTool insertModel:stu2 uid:@"Chavez" targetId:nil];
XCTAssertTrue(result3);
}
創(chuàng)建對應(yīng)的數(shù)據(jù)庫以及表格,向數(shù)據(jù)庫插入3條數(shù)據(jù),最終我們看到測試成功,并打開對應(yīng)的數(shù)據(jù)庫表格進(jìn)行驗證得到下圖,成功!

2、數(shù)據(jù)庫查詢
面向sql的API我們可以分為兩類,一類為查詢操作,一類為非查詢操作,也就是執(zhí)行語句,執(zhí)行語句在之前已經(jīng)實現(xiàn)了,現(xiàn)在我們來實現(xiàn)查詢操作,首先提供思路:
- 1.打開數(shù)據(jù)庫
- 2.預(yù)執(zhí)行sql語句
- 3.綁定數(shù)據(jù)
- 4.執(zhí)行遍歷查詢(在這里我們會將每一條數(shù)據(jù)封裝成字典{字段類型key : 字段值value ...},然后加入數(shù)組中保存返回)
- 5.重置
- 6.釋放資源,關(guān)閉數(shù)據(jù)庫
sql3為查詢提供了以下兩個方法
// 預(yù)執(zhí)行sql語句、準(zhǔn)備語句
SQLITE_API int sqlite3_prepare_v2(
sqlite3 *db, /* 數(shù)據(jù)庫的操作句柄 */
const char *zSql, /* sql語句 */
int nByte, /* 參數(shù)2sql語句取出多少字節(jié)的長度。-1為自動計算 找到\0結(jié)束符*/
sqlite3_stmt **ppStmt, /* 伴隨指針的地址 */
const char **pzTail /* 參數(shù)2減去參數(shù)3剩余的sql語句*/
);
// 執(zhí)行伴隨指針,如果結(jié)果為SQLITE_ROW,表示還有下一條數(shù)據(jù),伴隨指針指向下一條數(shù)據(jù),否則結(jié)束循環(huán)
SQLITE_API int sqlite3_step(sqlite3_stmt*);
在CWDatabase封裝一個方法,執(zhí)行查詢操作
+ (NSMutableArray <NSMutableDictionary *>*)querySql:(NSString *)sql uid:(NSString *)uid {
// 1、打開數(shù)據(jù)庫
if (![self openDB:uid]) {
return nil;
}
// 2、預(yù)執(zhí)行語句
sqlite3_stmt *ppStmt = 0x00; //伴隨指針
if (sqlite3_prepare_v2(cw_database, sql.UTF8String, -1, &ppStmt, nil) != SQLITE_OK) {
NSLog(@"查詢準(zhǔn)備語句編譯失敗");
return nil;
}
// 3、綁定數(shù)據(jù),因為我們的sql語句中不帶有?用來賦值,所以不需要進(jìn)行綁定
// 4、執(zhí)行遍歷查詢
NSMutableArray *rowDicArray = [NSMutableArray array];
while (sqlite3_step(ppStmt) == SQLITE_ROW) { // SQLITE_ROW表示還有下一條數(shù)據(jù)
// 獲取有多少列(也就是一條數(shù)據(jù)有多少個字段)
int columnCount = sqlite3_column_count(ppStmt);
// 存儲一條數(shù)據(jù)的所有字段名與值 的字典
NSMutableDictionary *rowDict = [NSMutableDictionary dictionary];
// 遍歷數(shù)據(jù)庫一條數(shù)據(jù)所有字段
for (int i = 0; i < columnCount; i++) {
// 獲取字段名
NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name(ppStmt, i)];
// 獲取字段名對應(yīng)的類型
int type = sqlite3_column_type(ppStmt, i);
// 獲取對應(yīng)的值
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: // 二進(jìn)制
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];
}
// 5、重制(省略)
// 6、釋放資源,關(guān)閉數(shù)據(jù)庫
sqlite3_finalize(ppStmt);
[self closeDB];
return rowDicArray;
}
做完了面向sql的查詢之后,我們要將這個方法封裝一下,面向模型。我們在CWSqliteModelTool封裝一個方法
#pragma mark 查詢數(shù)據(jù)
// 查詢表內(nèi)所有數(shù)據(jù)
+ (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];
}
// 解析數(shù)據(jù)
+ (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;
}
然后我們測試查詢函數(shù)
- (void)testQueryModels {
NSArray *models = [CWSqliteModelTool queryAllModels:[Student class] uid:@"Chavez" targetId:nil];
NSLog(@"query models : %@",models);
XCTAssertNotNil(models);
}
目前Chavez數(shù)據(jù)庫的Student表一共存在3條我們剛剛插入進(jìn)去的數(shù)據(jù),執(zhí)行查詢的結(jié)果如果與數(shù)據(jù)庫內(nèi)的數(shù)據(jù)對應(yīng),則代表成功,最終我們得到如下打印:
// 數(shù)據(jù)里有3個模型,每個模型對應(yīng)的成員變量以及值都對應(yīng)上了,打印出來的不是Student模型的地址是因為我們重寫了Student模型的description函數(shù)
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"
)
測試結(jié)果表示我們的方法完全OK!
3、更新數(shù)據(jù)
在文章的開頭,我們已經(jīng)為這個方法做了很多的介紹了,我們要把創(chuàng)建數(shù)據(jù)庫、創(chuàng)建表格、插入數(shù)據(jù)、更新數(shù)據(jù)都整合在這個方法內(nèi),先說一下實現(xiàn)的步驟:
- 1.獲取表名,判斷數(shù)據(jù)庫是否存在對應(yīng)的表,不存在則創(chuàng)建
- 2.根據(jù)主鍵,判斷數(shù)據(jù)庫內(nèi)是否存在記錄
- 3.如果數(shù)據(jù)庫內(nèi)不存在記錄,進(jìn)行數(shù)據(jù)插入操作
- 4.如果數(shù)據(jù)庫內(nèi)存在記錄,進(jìn)行數(shù)據(jù)更新操作
首先我們來解決步驟1的問題,如何判斷數(shù)據(jù)庫內(nèi)是否存在對應(yīng)的表:我們要引入一個概念sqlite的系統(tǒng)表sqlite_master,每一個 SQLite 數(shù)據(jù)庫都有一個叫 sqlite_master 的表, 它定義數(shù)據(jù)庫的模式。這個表里面存儲著當(dāng)前數(shù)據(jù)庫中所有表的相關(guān)信息,比如表的名稱、用于創(chuàng)建此表的sql語句、索引、索引所屬的表、創(chuàng)建索引的sql語句等,所以我們只要對sqlite_master執(zhí)行查詢操作即可,在CWSqliteTableTool內(nèi)封裝一個方法:
+ (BOOL)isTableExists:(NSString *)tableName uid:(NSString *)uid{
// 去sqlite_master這個表里面去查詢創(chuàng)建此索引的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 插入或者更新數(shù)據(jù)
+ (BOOL)insertOrUpdateModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId {
// 獲取表名
Class cls = [model class];
NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
// 判斷數(shù)據(jù)庫是否存在對應(yīng)的表,不存在則創(chuàng)建
if (![CWSqliteTableTool isTableExists:tableName uid:uid]) {
[self createSQLTable:cls uid:uid targetId:targetId];
}
// 根據(jù)主鍵,判斷數(shù)據(jù)庫內(nèi)是否存在記錄
// 判斷對象是否返回主鍵信息
if (![cls respondsToSelector:@selector(primaryKey)]) {
NSLog(@"如果想要操作這個模型,必須要實現(xiàn)+ (NSString *)primaryKey;這個方法,來告訴我主鍵信息");
return NO;
}
// 獲取主鍵
NSString *primaryKey = [cls primaryKey];
if (!primaryKey) {
NSLog(@"你需要指定一個主鍵來創(chuàng)建數(shù)據(jù)庫表");
return NO;
}
// 模型中的主鍵的值
id primaryValue = [model valueForKeyPath:primaryKey];
// 查詢語句: NSString *checkSql = @"select * from 表名 where 主鍵 = '主鍵值' ";
NSString * checkSql = [NSString stringWithFormat:@"select * from %@ where %@ = '%@'",tableName,primaryKey,primaryValue];
// 執(zhí)行查詢語句,獲取結(jié)果
NSArray *result = [CWDatabase querySql:checkSql uid:uid];
// 獲取類的所有成員變量的名稱與類型
NSDictionary *nameTypeDict = [CWModelTool classIvarNameAndTypeDic:cls];
// 獲取所有成員變量的名稱,也就是sql語句字段名稱
NSArray *allIvarNames = nameTypeDict.allKeys;
// 獲取所有成員變量對應(yīng)的值
NSMutableArray *allIvarValues = [NSMutableArray array];
for (NSString *ivarName in allIvarNames) {
// 獲取對應(yīng)的值,暫時不考慮自定義模型和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) { // 表內(nèi)存在記錄,更新
// update 表名 set 字段1='字段1值',字段2='字段2的值'...where 主鍵 = '主鍵值'
execSql = [NSString stringWithFormat:@"update %@ set %@ where %@ = '%@'",tableName,[ivarNameValueArray componentsJoinedByString:@","],primaryKey,primaryValue];
}else { // 表內(nèi)不存在記錄,插入
// 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];
}
然后我們通過單元測試來測試這個方法,首先我們測試在數(shù)據(jù)不存在的時候的插入操作:
// 向Chavez數(shù)據(jù)庫的“Student國防科技大學(xué)”表內(nèi)插入兩條數(shù)據(jù)
- (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:@"國防科技大學(xué)"];
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:@"國防科技大學(xué)"];
XCTAssertTrue(result1);
}
我們打開數(shù)據(jù)庫軟件,按command+R刷新軟件,查看結(jié)果如下

插入數(shù)據(jù)測試成功,然后我們再為這個方法來寫一個更新數(shù)據(jù)的單元測試:
// 我們把數(shù)據(jù)庫里面stuId為110的中國公安的數(shù)據(jù)修改,還是調(diào)用同樣的方法
- (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:@"國防科技大學(xué)"];
XCTAssertTrue(result);
}
運(yùn)行之后將數(shù)據(jù)庫軟件進(jìn)行刷新,得到如下表格,發(fā)現(xiàn)stuId為110的數(shù)據(jù)已經(jīng)按照我們最新的模型進(jìn)行了修改,測試通過。
最終,我們將數(shù)據(jù)庫的創(chuàng)建、打開、建表、插入、更新操作都封裝成了一個方法,非常符合我們之前要求的,簡單、簡單、簡單無腦。
如果你想要保存一條數(shù)據(jù)到本地數(shù)據(jù)庫,只需要執(zhí)行這個方法:
+ (BOOL)insertOrUpdateModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId;
如果你想要更新本地數(shù)據(jù)庫的一條數(shù)據(jù),也只需要執(zhí)行上面的方法,絲毫不需要關(guān)心數(shù)據(jù)庫是如何創(chuàng)建的,數(shù)據(jù)庫表是如何創(chuàng)建的等。。
如果你想要查詢數(shù)據(jù)庫某個表里面的所有數(shù)據(jù),只需要執(zhí)行這個方法:
+ (NSArray *)queryAllModels:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId;
后期我們會為查詢增加一些條件,來滿足更多的查詢場景。
4.本篇結(jié)束
在此,數(shù)據(jù)庫的增刪查改我們通過封裝出來的兩個方法,便捷的實現(xiàn)了其中3項,一些拓展的功能選擇在后面再完善吧。
在下一篇文章,我們會實現(xiàn)數(shù)據(jù)庫的刪除、數(shù)據(jù)庫遷移,以及再完善一下細(xì)節(jié),在更后面的文章,我們會實現(xiàn)模型嵌套對象,數(shù)組、字典潛逃對象的情況以及多線程安全的處理。。
github地址
本次的代碼,tag為1.1.0,你可以在release下找到對應(yīng)的tag下載下來
最后覺得有用的同學(xué),希望能給本文點個喜歡,給github點個star以資鼓勵,謝謝大家。
PS: 因為我也是一邊封裝,一邊寫文章。效率可能比較低,問題也會有,歡迎大家向我拋issue,有更好的思路也歡迎大家留言!
最后再為大家提供上一篇文章的地址。從0開始弄一個面向OC數(shù)據(jù)庫(一)