首先,在上一篇文章从0开始弄一个面向OC数据库(一),咱们实现了数据库的建立、打开、关闭、经过runtime获取模型全部成员变量建表功能。本次代码解决了一个上个版本代码存在的bug,当传targetId为nil的时候,数据库语句执行失败的问题, 其次,本篇文章要实现的功能有:git
这个方法的最终形态:若是是一个从零开始的状态,没有任何数据库,这个方法会替咱们 建立对应的数据库,建立对应的表格,插入对应的数据,若是存在对应的数据库、表、数据,则进行更新github
插入数据的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条数据,最终咱们看到测试成功,并打开对应的数据库表格进行验证获得下图,成功!数组
面向sql的API咱们能够分为两类,一类为查询操做,一类为非查询操做,也就是执行语句,执行语句在以前已经实现了,如今咱们来实现查询操做,首先提供思路:安全
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!
在文章的开头,咱们已经为这个方法作了不少的介绍了,咱们要把建立数据库、建立表格、插入数据、更新数据都整合在这个方法内,先说一下实现的步骤:
+ (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刷新软件,查看结果以下
// 咱们把数据库里面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的数据已经按照咱们最新的模型进行了修改,测试经过。
若是你想要保存一条数据到本地数据库,只须要执行这个方法:
+ (BOOL)insertOrUpdateModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId;
复制代码
若是你想要更新本地数据库的一条数据,也只须要执行上面的方法,丝绝不须要关心数据库是如何建立的,数据库表是如何建立的等。。
若是你想要查询数据库某个表里面的全部数据,只须要执行这个方法:
+ (NSArray *)queryAllModels:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId;
复制代码
后期咱们会为查询增长一些条件,来知足更多的查询场景。
在此,数据库的增删查改咱们经过封装出来的两个方法,便捷的实现了其中3项,一些拓展的功能选择在后面再完善吧。 在下一篇文章,咱们会实现数据库的删除、数据库迁移,以及再完善一下细节,在更后面的文章,咱们会实现模型嵌套对象,数组、字典潜逃对象的状况以及多线程安全的处理。。
github地址 本次的代码,tag为1.1.0,你能够在release下找到对应的tag下载下来
最后以为有用的同窗,但愿能给本文点个喜欢,给github点个star以资鼓励,谢谢你们。
PS: 由于我也是一边封装,一边写文章。效率可能比较低,问题也会有,欢迎你们向我抛issue,有更好的思路也欢迎你们留言!
最后再为你们提供上一篇文章的地址。从0开始弄一个面向OC数据库(一)