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