上個版本為了增加用戶體驗,部分頁面集成了離線緩存數(shù)據(jù)功能渐裂,于是就在項目里使用了數(shù)據(jù)庫管理離線數(shù)據(jù)豺旬。下面交大家一步步學會使用FMDB,以及FMDB的二次封裝柒凉,同事把我二次封裝的數(shù)據(jù)庫放出來族阅,希望能夠幫助大家快速學習,集成數(shù)據(jù)庫功能吧膝捞。
一.首先看一下STDB文件結(jié)構(gòu)
-
Table.h
主要放一些Table的創(chuàng)建語句坦刀, 方便管理我的數(shù)據(jù)庫各張表創(chuàng)建 -
DBDefine.h
主要放一些表名的宏定義,數(shù)據(jù)庫版本號,數(shù)據(jù)庫名字等等鲤遥,方便我們在使用數(shù)據(jù)庫過程中更直觀管理版本和各種表 -
STDBTool.h,STDBTool.m
具體封裝實現(xiàn)代碼

二.具體實現(xiàn)功能
1 . STDBTool.h
頭文件看一下
我定義了三個FMDatabaseQueue 因為實際操作中我需要數(shù)據(jù)庫嵌套沐寺,如果只使用一個FMDatabaseQueue 將會陷入死循環(huán),下面看一下1
FMDatabaseQueue
源碼分析一下原因:
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
默認是串行隊列,數(shù)據(jù)庫操作的時候FMDB源碼如下圖
同步執(zhí)行串行隊列 block塊里按著順序執(zhí)行盖奈。

任務1執(zhí)行 ——>任務二等待任務一執(zhí)行完畢執(zhí)行混坞,任務一等待任務二執(zhí)行完畢執(zhí)行,死鎖钢坦。 如果新建一個串行隊列

這樣就沒有問題究孕。 至于同步異步并行串行網(wǎng)上也有很多,不在一一介紹啦爹凹。
2 . STDBTool的初始化 厨诸,很顯然 STDBTool用的是單例啦, 看一下alloc方法
這里面也就是創(chuàng)建一下數(shù)據(jù)庫文件逛万,設(shè)置數(shù)據(jù)庫版本號。
當數(shù)據(jù)庫有新表需要增加時批钠,我們需要改變數(shù)據(jù)庫版本號宇植,對數(shù)據(jù)庫進行升級。
- .查詢數(shù)據(jù)庫當前版本
NSString * sql = [NSString stringWithFormat:@"PRAGMA user_version"];
FMResultSet * rs = [db executeQuery:sql];
int nVersion = 0;
while ([rs next]) {
nVersion = [rs intForColumn:@"user_version"];
}
- 與宏定義數(shù)據(jù)庫版本不一致 需要更新 設(shè)置 數(shù)據(jù)庫版本號
NSString *sql = [NSString stringWithFormat:@"PRAGMA user_version = %ld",(long)newVersion];
BOOL ret = [db executeUpdate:sql];
STDBTool初始化就這些吧埋心。也沒什么難點指郁,主要是跟大家一起回顧一下。
3 .實現(xiàn)的數(shù)據(jù)庫數(shù)據(jù)操作功能
一般數(shù)據(jù)庫操作無非是增刪改查這些基本操作拷呆,當然我的封裝也是基于實現(xiàn)這些功能的闲坎。但是根據(jù)具體情況我們需要進行封裝。
- 執(zhí)行單個sql語句時候茬斧,不需要使用事務處理腰懂,我們需要知道操作類型,這里我寫了個枚舉type 便于區(qū)分
-(void)executeSQL:(NSString *)sqlStr actionType:(ST_DB_ActionType)actionType withBlock:(void(^)(BOOL bRet, FMResultSet *rs, NSString *msg))block{
[_dbQueue inDatabase:^(FMDatabase *db) {
if (actionType == ST_DB_SELECT) {
//查詢語句 需要返回記錄集
FMResultSet * rs = [db executeQuery:sqlStr];
if ([db hadError]) {
block(NO,rs,[db lastErrorMessage]);
NSLog(@"executeSQL error %d: %@",[db lastErrorCode],[db lastErrorMessage]);
}else{
block(YES,rs,nil);
}
}else{
//更新操作 只關(guān)心操作是否執(zhí)行成功项秉,不關(guān)心記錄集 返回布爾值 無執(zhí)行結(jié)果
BOOL ret = [db executeUpdate:sqlStr];
if ([db hadError]) {
block(NO,nil,[db lastErrorMessage]);
NSLog(@"executeSQL error %d: %@",[db lastErrorCode],[db lastErrorMessage]);
}else{
block(ret,nil,nil);
}
}
}];
}
- 根據(jù)查詢結(jié)果 確定是更新還是新增操作绣溜,只需要知道是否操作成功,不關(guān)心結(jié)果集 只處理一個查詢更新娄蔼,不需要事務處理
- (void)executeRelevanceSql:(NSArray *)sqlList withBlock:(void(^)(BOOL ret,NSString * errMsg))block{
__block BOOL ret;
[_dbQueue inDatabase:^(FMDatabase *db) {
FMResultSet * rs = [db executeQuery:sqlList[0]];
if ([db hadError]) {
block(NO,[db lastErrorMessage]);
NSLog(@"da_error_%@",[db lastErrorMessage]);
}
int nCount = 0;
if ([rs next]) {
//獲取查詢數(shù)據(jù)的個數(shù)
nCount = [rs intForColumnIndex:0];
}
[rs close];
NSString * nextSqlString = nil;
if (nCount > 0) {
//查詢到了結(jié)果 執(zhí)行update操作
nextSqlString = sqlList[1];
}else{
//查詢無結(jié)果 執(zhí)行 insert into 操作
nextSqlString = sqlList[2];
}
ret = [db executeUpdate:nextSqlString];
if ([db hadError]) {
block(NO,[db lastErrorMessage]);
NSLog(@"da_error_%@",[db lastErrorMessage]);
}else{
block(ret, nil);
}
}];
}
注:sql語句數(shù)組怖喻,sqlList[0]
查詢select語句 sqList[1]
update更新語句 sqlList[2]
insert into 插入語句
- 根據(jù)查詢結(jié)果 確定是更新還是新增操作,只需要知道是否操作成功岁诉,不關(guān)心結(jié)果集 只處理一個查詢更新锚沸,不需要事務處理
- (void)executeRelevanceSql:(NSArray *)sqlList withBlock:(void(^)(BOOL ret,NSString * errMsg))block{
__block BOOL ret;
[_dbQueue inDatabase:^(FMDatabase *db) {
FMResultSet * rs = [db executeQuery:sqlList[0]];
if ([db hadError]) {
block(NO,[db lastErrorMessage]);
NSLog(@"da_error_%@",[db lastErrorMessage]);
}
int nCount = 0;
if ([rs next]) {
//獲取查詢數(shù)據(jù)的個數(shù)
nCount = [rs intForColumnIndex:0];
}
[rs close];
NSString * nextSqlString = nil;
if (nCount > 0) {
//查詢到了結(jié)果 執(zhí)行update操作
nextSqlString = sqlList[1];
}else{
//查詢無結(jié)果 執(zhí)行 insert into 操作
nextSqlString = sqlList[2];
}
ret = [db executeUpdate:nextSqlString];
if ([db hadError]) {
block(NO,[db lastErrorMessage]);
NSLog(@"da_error_%@",[db lastErrorMessage]);
}else{
block(ret, nil);
}
}];
}
注: sql語句數(shù)組,sqlList[0]查詢select語句 sqList1update更新語句 sqlList2 insert into 插入語句
4 . sqlList 是一個二維數(shù)組涕癣,每一個成員包含三個sql語句哗蜈,分別是查詢,更新,插入恬叹,并且根據(jù)查詢結(jié)果返回是執(zhí)行更新 還是 插入操作候生。使用dbQueue2 用于直接調(diào)用。批量處理绽昼,使用事務唯鸭。
- (void)executeDbQueue2RelevanceTransactionSqlList:(NSArray *)sqlList withBlock:(void(^)(BOOL bRet, NSString *msg, BOOL *bRollback))block{
__block BOOL ret = NO;
[_dbQueue2 inTransaction:^(FMDatabase *db, BOOL *rollback) {
for (NSArray * singleSqlList in sqlList ) {
FMResultSet * rs = [db executeQuery:singleSqlList[0]];
if ([db hadError]) {
block(NO,[db lastErrorMessage],rollback);
NSLog(@"da_error_%@",[db lastErrorMessage]);
}else{
int nCount = 0;
while ([rs next]){
nCount = [rs intForColumnIndex:0];
}
[rs close];
NSString * nextSqlString = nil;
if (nCount > 0){
//執(zhí)行更新
nextSqlString = singleSqlList[1];
}
else{
//執(zhí)行插入
nextSqlString = singleSqlList[2];
}
ret = [db executeUpdate:nextSqlString];
if ([db hadError])
{
block(NO, [db lastErrorMessage], rollback);
NSLog(@"executeSql Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
}
}
}
block(ret, nil, rollback);
}];
}
注:sql語句數(shù)組,sqlArr[i][0]:查詢語句硅确;sqlArri:update語句目溉;sqlArri:insert into語句
5.sql語句數(shù)組中每個成員有2條語句,第一條是select語句菱农,第二條是insert into語句缭付,根據(jù)第一個sql的執(zhí)行結(jié)果確定第二條語句是否執(zhí)行。根據(jù)查詢結(jié)果確定是否新增循未,批量處理陷猫,使用事務處理,不需要返回記錄集使用dbQueue2的妖,用于程序中直接調(diào)用(非封裝在其他方法中)
-(void)executeInsertTransactionSqlList:(NSArray *)sqlStrList withBlock:(void(^)(BOOL bRet, NSString *msg, BOOL *bRollback))block
{
__block BOOL ret = NO;
NSLog(@"開始啦---");
[_dbQueue2 inTransaction:^(FMDatabase *db, BOOL *rollback){
for (NSArray *sqlArray in sqlStrList){
FMResultSet *rs = [db executeQuery:[sqlArray objectAtIndex:0]];
if ([db hadError]){
block(NO, [db lastErrorMessage], rollback);
NSLog(@"executeSql Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
}
int nCount = 0;
while ([rs next]){
nCount = [rs intForColumnIndex:0];
}
[rs close];
if (nCount <= 0){
ret = [db executeUpdate:[sqlArray objectAtIndex:1]];
if ([db hadError])
{
block(NO, [db lastErrorMessage], rollback);
NSLog(@"executeSql Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
}
}
}
block(ret, nil, rollback);
}];
}
注:sql語句數(shù)組绣檬,sqlArr[i][0]:查詢語句;sqlArri:insert into語句
6.批量處理更新或者新增sql語句嫂粟,不需要返回記錄集 無事務處理
- (void)executeSQLList:(NSArray *)sqlStrList db:(FMDatabase *)db withBlock:(void(^)(BOOL bRet, NSString *msg))block{
__block BOOL bRet = NO;
for (NSString * sqlString in sqlStrList) {
bRet = [db executeUpdate:sqlString];
if ([db hadError]) {
block(bRet,[db lastErrorMessage]);
NSLog(@"executeSQLList Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
break;
}
}
block(bRet,nil);
}
注: sql語句數(shù)組update或者insert into語句
7.批量處理更新或者新增sql語句娇未,并且不需要返回記錄集,使用事務處理
-(void)executeTransactionSqlList:(NSArray *)sqlStrList withBlock:(void(^)(BOOL bRet, NSString *msg, BOOL *bRollback))block
{
__block BOOL bRet = NO;
[_dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback){
for (NSString *sqlStr in sqlStrList)
{
bRet = [db executeUpdate:sqlStr];
if ([db hadError])
{
block(bRet, [db lastErrorMessage], rollback);
NSLog(@"executeSQLList Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
break;
}
}
block(bRet, nil, rollback);
}];
}
注:sql語句數(shù)組update或者insert into語句
還有幾個方法在防止死循環(huán)嵌套 類似的函數(shù)星虹。
github地址可以直接下載使用零抬,感覺有用的話star一下,謝謝大家宽涌。希望能幫到大家平夜。