针对JQFMDB(二次封装FMDB)的解读
FMDB 是针对 Sqlite 的封装,极其好用。但是为了更一步的简单使用,有必要对 FMDB 再进行一次封装,减去手写数据库语句的烦恼。JQFMDB 基本就是为了达到这个功能而生。
1 /** 2 (主键id,自动创建) 返回最后插入的primary key id 3 @param tableName 表的名称 4 */ 5 - (NSInteger)lastInsertPrimaryKeyId:(NSString *)tableName;
首先是主键,JQFMDB 会默认在创建表的时候添加一个名为: pkid 的主键。这个方法返回当前最大的主键:
1 - (NSInteger)lastInsertPrimaryKeyId:(NSString *)tableName 2 { 3 NSString *sqlstr = [NSString stringWithFormat:@"SELECT * FROM %@ where pkid = (SELECT max(pkid) FROM %@)", tableName, tableName]; 4 FMResultSet *set = [_db executeQuery:sqlstr]; 5 while ([set next]) 6 { 7 return [set longLongIntForColumn:@"pkid"]; 8 } 9 return 0; 10 }
1 /** 2 单例方法创建数据库, 如果使用shareDatabase创建,则默认在NSDocumentDirectory下创建JQFMDB.sqlite, 但只要使用这三个方法任意一个创建成功, 之后即可使用三个中任意一个方法获得同一个实例,参数可随意或nil 3 4 dbName 数据库的名称 如: @"Users.sqlite", 如果dbName = nil,则默认dbName=@"JQFMDB.sqlite" 5 dbPath 数据库的路径, 如果dbPath = nil, 则路径默认为NSDocumentDirectory 6 */ 7 + (instancetype)shareDatabase; 8 + (instancetype)shareDatabase:(NSString *)dbName; 9 + (instancetype)shareDatabase:(NSString *)dbName path:(NSString *)dbPath;
这里的单例只是用了一个静态的 JQFMDB 变量。这里的 JQFMDB 有三个属性:
1 @property (nonatomic, strong)NSString *dbPath; 2 @property (nonatomic, strong)FMDatabaseQueue *dbQueue; 3 @property (nonatomic, strong)FMDatabase *db;
在创建 JQFMDB 单例对象的时候会给 _dbPath 和 _db 赋值。并重写了 _dbQueue 的 get 方法,根据 _dbPath 创建 _dbQueue。然后用了一句:
1 self.db = [fmdb valueForKey:@"_db"];
把 _db 进行了重新赋值,这里的 fmdb 是根据 _dbPath 创建的 FMDatabaseQueue 变量:
1 FMDatabaseQueue *fmdb = [FMDatabaseQueue databaseQueueWithPath:_dbPath];
且在单例方法实现的结尾用 open 判断数据库是否创建成功:
1 if (![jqdb.db open]) { 2 NSLog(@"database can not open !"); 3 return nil; 4 };
1 /** 2 非单例方法创建数据库 3 4 @param dbName 数据库的名称 如: @"Users.sqlite" 5 dbPath 数据库的路径, 如果dbPath = nil, 则路径默认为NSDocumentDirectory 6 */ 7 - (instancetype)initWithDBName:(NSString *)dbName; 8 - (instancetype)initWithDBName:(NSString *)dbName path:(NSString *)dbPath;
常规的创建数据库对象。
1 /** 2 创建表 通过传入的model或dictionary(如果是字典注意类型要写对),虽然都可以不过还是推荐以下都用model 3 4 @param tableName 表的名称 5 @param parameters 设置表的字段,可以传model(runtime自动生成字段)或字典(格式:@{@"name":@"TEXT"}) 6 @return 是否创建成功 7 */ 8 - (BOOL)jq_createTable:(NSString *)tableName dicOrModel:(id)parameters;
1 /** 2 同上, 3 @param nameArr 不允许model或dic里的属性/key生成表的字段,如:nameArr = @[@"name"],则不允许名为name的属性/key 生成表的字段 4 5 */ 6 - (BOOL)jq_createTable:(NSString *)tableName dicOrModel:(id)parameters excludeName:(NSArray *)nameArr;
这里是建表,方法二 里面有个 nameArr 的数组参数,主要用来记录不希望被添加进表里的字段名。
主要看 parameters 参数,这个 id 类型的参数可以是一个 model 对象或者一个字典或者一个 [model Class] 的字符串。当是一个字典的时候:字典的 key 就是表的字段名,对应的 value 就是该字段的类型,如: "TEXT" 等。
当 parameters 是一个 model 对象或者 [model Class] 的时候,主要使用 runtime 来遍历模型类的各个属性和属性对应的类型:
1 - (NSDictionary *)modelToDictionary:(Class)cls excludePropertyName:(NSArray *)nameArr 2 { 3 NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithCapacity:0]; 4 unsigned int outCount; 5 objc_property_t *properties = class_copyPropertyList(cls, &outCount); 6 for (int i = 0; i < outCount; i++) { 7 8 NSString *name = [NSString stringWithCString:property_getName(properties[i]) encoding:NSUTF8StringEncoding]; 9 if ([nameArr containsObject:name]) continue; 10 11 NSString *type = [NSString stringWithCString:property_getAttributes(properties[i]) encoding:NSUTF8StringEncoding]; 12 13 id value = [self propertTypeConvert:type]; 14 if (value) { 15 [mDic setObject:value forKey:name]; 16 } 17 18 } 19 free(properties); 20 21 return mDic; 22 }
1 - (NSString *)propertTypeConvert:(NSString *)typeStr 2 { 3 NSString *resultStr = nil; 4 if ([typeStr hasPrefix:@"T@\"NSString\""]) { 5 resultStr = SQL_TEXT; 6 } else if ([typeStr hasPrefix:@"T@\"NSData\""]) { 7 resultStr = SQL_BLOB; 8 } else if ([typeStr hasPrefix:@"Ti"]||[typeStr hasPrefix:@"TI"]||[typeStr hasPrefix:@"Ts"]||[typeStr hasPrefix:@"TS"]||[typeStr hasPrefix:@"T@\"NSNumber\""]||[typeStr hasPrefix:@"TB"]||[typeStr hasPrefix:@"Tq"]||[typeStr hasPrefix:@"TQ"]) { 9 resultStr = SQL_INTEGER; 10 } else if ([typeStr hasPrefix:@"Tf"] || [typeStr hasPrefix:@"Td"]){ 11 resultStr= SQL_REAL; 12 } 13 14 return resultStr; 15 }
当拿到存放数据表的字段名和字段类型的 dic 的时候就开始,编写数据库语句执行数据库语句创建表:
1 NSMutableString *fieldStr = [[NSMutableString alloc] initWithFormat:@"CREATE TABLE %@ (pkid INTEGER PRIMARY KEY,", tableName]; 2 3 int keyCount = 0; 4 for (NSString *key in dic) { 5 6 keyCount++; 7 if ((nameArr && [nameArr containsObject:key]) || [key isEqualToString:@"pkid"]) { 8 continue; 9 } 10 if (keyCount == dic.count) { 11 [fieldStr appendFormat:@" %@ %@)", key, dic[key]]; 12 break; 13 } 14 15 [fieldStr appendFormat:@" %@ %@,", key, dic[key]]; 16 } 17 18 BOOL creatFlag; 19 creatFlag = [_db executeUpdate:fieldStr];
1 /** 2 增加: 向表中插入数据 3 4 @param tableName 表的名称 5 @param parameters 要插入的数据,可以是model或dictionary(格式:@{@"name":@"小李"}) 6 @return 是否插入成功 7 */ 8 - (BOOL)jq_insertTable:(NSString *)tableName dicOrModel:(id)parameters;
1 - (NSArray *)getColumnArr:(NSString *)tableName db:(FMDatabase *)db 2 { 3 NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:0]; 4 5 FMResultSet *resultSet = [db getTableSchema:tableName]; 6 7 while ([resultSet next]) { 8 [mArr addObject:[resultSet stringForColumn:@"name"]]; 9 } 10 11 return mArr; 12 }
首先拿到表里面的所有的字段名字放进数组里面。
1 - (NSDictionary *)getModelPropertyKeyValue:(id)model tableName:(NSString *)tableName clomnArr:(NSArray *)clomnArr 2 { 3 NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithCapacity:0]; 4 unsigned int outCount; 5 objc_property_t *properties = class_copyPropertyList([model class], &outCount); 6 7 for (int i = 0; i < outCount; i++) { 8 9 NSString *name = [NSString stringWithCString:property_getName(properties[i]) encoding:NSUTF8StringEncoding]; 10 if (![clomnArr containsObject:name]) { 11 continue; 12 } 13 14 id value = [model valueForKey:name]; 15 if (value) { 16 [mDic setObject:value forKey:name]; 17 } 18 } 19 free(properties); 20 21 return mDic; 22 }
当传入的是模型的时候,拿到模型的 key 和 value 值,这里的模型和上面的创建表的模型是有区别的,这里的模型是对属性赋过值的,创建表的模型是不赋值的,建表时主要是使用模型的属性名和属性类型去和数据表的字段对应创建。这里传的模型则是为了拿到属性名并且拿到属性值给表里的字段赋值。
当拿到属性名和属性值时候,就是编写数据库语句执行数据库语句,向表中插入数据。
1 /** 2 删除: 根据条件删除表中数据 3 4 @param tableName 表的名称 5 @param format 条件语句, 如:@"where name = '小李'" 6 @return 是否删除成功 7 */ 8 - (BOOL)jq_deleteTable:(NSString *)tableName whereFormat:(NSString *)format, ...;
删除表中的数据。
1 #define va_start(ap, param) __builtin_va_start(ap, param)
va_start,函数名称,读取可变参数的过程其实就是在堆栈中,使用指针,遍历堆栈段中的参数列表,从低地址到高地址一个一个地把参数内容读出来的过程·
1 #define va_end(ap) __builtin_va_end(ap)
主要是这里的 format 参数:
示例:
1 [NSString stringWithFormat:@"where device_id = '%@'", model.device_id]
即删除数据表中 device_id 等于 model.device_id 的那条数据。
1 - (BOOL)jq_deleteTable:(NSString *)tableName whereFormat:(NSString *)format, ... 2 { 3 va_list args; 4 va_start(args, format); 5 NSString *where = format?[[NSString alloc] initWithFormat:format locale:[NSLocale currentLocale] arguments:args]:format; 6 va_end(args); 7 BOOL flag; 8 NSMutableString *finalStr = [[NSMutableString alloc] initWithFormat:@"delete from %@ %@", tableName,where]; 9 flag = [_db executeUpdate:finalStr]; 10 11 return flag; 12 }
1 /** 2 更改: 根据条件更改表中数据 3 4 @param tableName 表的名称 5 @param parameters 要更改的数据,可以是model或dictionary(格式:@{@"name":@"张三"}) 6 @param format 条件语句, 如:@"where name = '小李'" 7 @return 是否更改成功 8 */ 9 - (BOOL)jq_updateTable:(NSString *)tableName dicOrModel:(id)parameters whereFormat:(NSString *)format, ...;
1 /** 2 查找: 根据条件查找表中数据 3 4 @param tableName 表的名称 5 @param parameters 每条查找结果放入model(可以是[Person class] or @"Person" or Person实例)或dictionary中 6 @param format 条件语句, 如:@"where name = '小李'", 7 @return 将结果存入array,数组中的元素的类型为parameters的类型 8 */ 9 - (NSArray *)jq_lookupTable:(NSString *)tableName dicOrModel:(id)parameters whereFormat:(NSString *)format, ...; 10 11 /** 12 批量插入或更改 13 14 @param dicOrModelArray 要insert/update数据的数组,也可以将model和dictionary混合装入array 15 @return 返回的数组存储未插入成功的下标,数组中元素类型为NSNumber 16 */ 17 - (NSArray *)jq_insertTable:(NSString *)tableName dicOrModelArray:(NSArray *)dicOrModelArray;
1 // `删除表 2 - (BOOL)jq_deleteTable:(NSString *)tableName;
1 - (BOOL)jq_deleteTable:(NSString *)tableName 2 { 3 4 NSString *sqlstr = [NSString stringWithFormat:@"DROP TABLE %@", tableName]; 5 if (![_db executeUpdate:sqlstr]) 6 { 7 return NO; 8 } 9 return YES; 10 }
1 // `清空表 2 - (BOOL)jq_deleteAllDataFromTable:(NSString *)tableName;
1 - (BOOL)jq_deleteAllDataFromTable:(NSString *)tableName 2 { 3 4 NSString *sqlstr = [NSString stringWithFormat:@"DELETE FROM %@", tableName]; 5 if (![_db executeUpdate:sqlstr]) 6 { 7 return NO; 8 } 9 10 return YES; 11 }
1 // `是否存在表 2 - (BOOL)jq_isExistTable:(NSString *)tableName;
1 - (BOOL)jq_isExistTable:(NSString *)tableName 2 { 3 4 FMResultSet *set = [_db executeQuery:@"SELECT count(*) as 'count' FROM sqlite_master WHERE type ='table' and name = ?", tableName]; 5 while ([set next]) 6 { 7 NSInteger count = [set intForColumn:@"count"]; 8 if (count == 0) { 9 return NO; 10 } else { 11 return YES; 12 } 13 } 14 return NO; 15 }
1 // `表中共有多少条数据 2 - (int)jq_tableItemCount:(NSString *)tableName;
- (int)jq_tableItemCount:(NSString *)tableName { NSString *sqlstr = [NSString stringWithFormat:@"SELECT count(*) as 'count' FROM %@", tableName]; FMResultSet *set = [_db executeQuery:sqlstr]; while ([set next]) { return [set intForColumn:@"count"]; } return 0; }
1 // `关闭数据库 2 - (void)close; 3 // `打开数据库 (每次shareDatabase系列操作时已经open,当调用close后若进行db操作需重新open或调用shareDatabase) 4 - (void)open;
1 /** 2 增加新字段, 在建表后还想新增字段,可以在原建表model或新model中新增对应属性,然后传入即可新增该字段,该操作已在事务中执行 3 4 @param tableName 表的名称 5 @param parameters 如果传Model:数据库新增字段为建表时model所没有的属性,如果传dictionary格式为@{@"newname":@"TEXT"} 6 @param nameArr 不允许生成字段的属性名的数组 7 @return 是否成功 8 */ 9 - (BOOL)jq_alterTable:(NSString *)tableName dicOrModel:(id)parameters excludeName:(NSArray *)nameArr; 10 - (BOOL)jq_alterTable:(NSString *)tableName dicOrModel:(id)parameters;
1 - (BOOL)jq_alterTable:(NSString *)tableName dicOrModel:(id)parameters excludeName:(NSArray *)nameArr 2 { 3 __block BOOL flag; 4 [self jq_inTransaction:^(BOOL *rollback) { 5 if ([parameters isKindOfClass:[NSDictionary class]]) { 6 for (NSString *key in parameters) { 7 if ([nameArr containsObject:key]) { 8 continue; 9 } 10 flag = [_db executeUpdate:[NSString stringWithFormat:@"ALTER TABLE %@ ADD COLUMN %@ %@", tableName, key, parameters[key]]]; 11 if (!flag) { 12 *rollback = YES; 13 return; 14 } 15 } 16 17 } else { 18 Class CLS; 19 if ([parameters isKindOfClass:[NSString class]]) { 20 if (!NSClassFromString(parameters)) { 21 CLS = nil; 22 } else { 23 CLS = NSClassFromString(parameters); 24 } 25 } else if ([parameters isKindOfClass:[NSObject class]]) { 26 CLS = [parameters class]; 27 } else { 28 CLS = parameters; 29 } 30 NSDictionary *modelDic = [self modelToDictionary:CLS excludePropertyName:nameArr]; 31 NSArray *columnArr = [self getColumnArr:tableName db:_db]; 32 for (NSString *key in modelDic) { 33 if (![columnArr containsObject:key] && ![nameArr containsObject:key]) { 34 flag = [_db executeUpdate:[NSString stringWithFormat:@"ALTER TABLE %@ ADD COLUMN %@ %@", tableName, key, modelDic[key]]]; 35 if (!flag) { 36 *rollback = YES; 37 return; 38 } 39 } 40 } 41 } 42 }]; 43 44 return flag; 45 }
1 flag = [_db executeUpdate:[NSString stringWithFormat:@"ALTER TABLE %@ ADD COLUMN %@ %@", tableName, key, modelDic[key]]];
新增字段的表名、新增字段的名字、新增字段的类型。
1 /** 2 将操作语句放入block中即可保证线程安全, 如: 3 4 Person *p = [[Person alloc] init]; 5 p.name = @"小李"; 6 [jqdb jq_inDatabase:^{ 7 [jqdb jq_insertTable:@"users" dicOrModel:p]; 8 }]; 9 */ 10 - (void)jq_inDatabase:(void (^)(void))block; 11 12 13 /** 14 事务: 将操作语句放入block中可执行回滚操作(*rollback = YES;) 15 16 Person *p = [[Person alloc] init]; 17 p.name = @"小李"; 18 19 for (int i=0,i < 1000,i++) { 20 [jq jq_inTransaction:^(BOOL *rollback) { 21 BOOL flag = [jq jq_insertTable:@"users" dicOrModel:p]; 22 if (!flag) { 23 *rollback = YES; //只要有一次不成功,则进行回滚操作 24 return; 25 } 26 }]; 27 } 28 29 */ 30 - (void)jq_inTransaction:(void(^)(BOOL *rollback))block;
1 - (void)jq_inDatabase:(void(^)(void))block 2 { 3 4 [[self dbQueue] inDatabase:^(FMDatabase *db) { 5 block(); 6 }]; 7 } 8 9 - (void)jq_inTransaction:(void(^)(BOOL *rollback))block 10 { 11 12 [[self dbQueue] inTransaction:^(FMDatabase *db, BOOL *rollback) { 13 block(rollback); 14 }]; 15 16 }
在 Queue 的 inDatabase 里面做操作。
END
参考连接:http://blog.csdn.net/jk110333/article/details/41940177
http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html