目錄:
- 序
- 類文件解讀
- 更多你不知道的API
- 關(guān)于事物的使用
- 數(shù)據(jù)庫(kù)升級(jí)
序
只要是擼過(guò)一遍FMDB的人肌蜻,基本都知道怎么用,增蒋搜、刪、改酸休、查祷杈,偶爾涉及多表操作,多條件查詢宿刮,搜搜sql語(yǔ)句私蕾,基本上沒(méi)什么問(wèn)題,但是你以為這就夠了么磕潮?我曾經(jīng)也是這樣以為的容贝。
簡(jiǎn)單問(wèn)一下:
1、除了執(zhí)行斤富、更新满力,你還用到FMDB中的哪些API(例如:批處理)
2轻纪、FMDB中事物的代碼實(shí)現(xiàn)方法?
3叠纷、數(shù)據(jù)庫(kù)升級(jí)是怎么操作的?
4我擂、va_list是什么東西缓艳?
如果你全都知道看峻,那么可以略過(guò)這篇文章,如果你和我當(dāng)初一樣溪窒,只知道封裝個(gè)增冯勉、刪、改宛瞄、查方法交胚,我建議最好還是讀一下
一、類文件
文件名 | 描述 |
---|---|
FMDatabase | 一個(gè)FMDatabase對(duì)象就代表一個(gè)單獨(dú)的SQLite數(shù)據(jù)庫(kù) |
FMResultSet | 使用FMDatabase執(zhí)行查詢后的結(jié)果集合 |
FMDatabaseQueue | 用于在多線程中執(zhí)行多個(gè)查詢或更新杯活,它是線程安全的 |
FMDatabaseAdditions | 新增對(duì)查詢結(jié)果只返回單個(gè)值的方法進(jìn)行簡(jiǎn)化旁钧,對(duì)表互拾、列是否存在,版本號(hào)彤委,校驗(yàn)SQL等等功能 |
二或衡、更多你不知道的API
很長(zhǎng)時(shí)間以來(lái)我只知道這兩個(gè)方法车遂,一個(gè)更新舶担,一個(gè)執(zhí)行彬呻,夠用啦
db executeQuery:<#(nonnull NSString *), ...#>
db executeUpdate:<#(nonnull NSString *), ...#>
- 更新(create、drop剪况、insert蒲跨、update、delete)
- 執(zhí)行(select)
直到有一天沒(méi)事兒的時(shí)候看了看源碼孙咪,才發(fā)現(xiàn)巡语,雖然這個(gè)庫(kù)很輕量級(jí),但是我看到的依舊是冰山一角荤堪,接下來(lái)讓我看看它還有哪些能讓我眼前一亮的東西理澎。
FMDatabase主要API解讀
查詢
- (FMResultSet * _Nullable)executeQuery:(NSString*)sql, ...;
- (FMResultSet * _Nullable)executeQueryWithFormat:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
- (FMResultSet * _Nullable)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
- (FMResultSet * _Nullable)executeQuery:(NSString *)sql values:(NSArray * _Nullable)values error:(NSError * _Nullable __autoreleasing *)error;
- (FMResultSet * _Nullable)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary * _Nullable)arguments;
- (FMResultSet * _Nullable)executeQuery:(NSString *)sql withVAList:(va_list)args;
更新
- (BOOL)executeUpdate:(NSString*)sql, ...;
- (BOOL)executeUpdateWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
- (BOOL)executeUpdate:(NSString*)sql values:(NSArray * _Nullable)values error:(NSError * _Nullable __autoreleasing *)error;
- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments;
- (BOOL)executeUpdate:(NSString*)sql withVAList: (va_list)args;
方法解讀
1糠爬、該方法的參數(shù)必須是對(duì)象执隧,不能為基礎(chǔ)數(shù)據(jù)類型,否則就會(huì)崩潰
- (BOOL)executeUpdate:(NSString*)sql, ...;
錯(cuò)誤寫法:?
[_dataBaseQueue inDatabase:^(FMDatabase * _Nonnull db) {
[db executeUpdate:@"INSERT INTO usertable VALUES (?, ? , ?)", 1, @"lizhiqiang", 25];
}];
2、如果需要插入基礎(chǔ)數(shù)據(jù)類型峦嗤,要么自己做一下轉(zhuǎn)換屋摔,要么調(diào)用以下方法
- (FMResultSet * _Nullable)executeQueryWithFormat:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
[_dataBaseQueue inDatabase:^(FMDatabase * _Nonnull db) {
[db open];
[db executeUpdateWithFormat:@"INSERT INTO usertable VALUES (%d, %@ , %d)", 1, @"lizhiqiang", 25];
[db close];
}];
3、這個(gè)沒(méi)什么可說(shuō)的装黑,數(shù)組參數(shù),直接上代碼
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
[_dataBaseQueue inDatabase:^(FMDatabase * _Nonnull db) {
[db open];
[db executeUpdate:@"INSERT INTO usertable VALUES (?, ? , ?)" withArgumentsInArray:@[@2, @"yanghuixue", @26]];
[db close];
}];
4糠睡、比方法3多了error指針參數(shù)疚颊,記錄更新失敗
- (BOOL)executeUpdate:(NSString*)sql values:(NSArray * _Nullable)values error:(NSError * _Nullable __autoreleasing *)error;
5、注意均抽,這個(gè)mark一下母截,參數(shù)為字典清寇,寫法變了护蝶,并且插入字段必須與字典key相對(duì)應(yīng)
- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments;
NSDictionary *testDict = @{
@"id" : @14,
@"name" : @"ly",
@"age" : @15
};
[_dataBaseQueue inDatabase:^(FMDatabase * _Nonnull db) {
[db open];
[db executeUpdate:@"INSERT INTO usertable VALUES(:id, :name, :age)" withParameterDictionary:testDict];
[db close];
}];
6、va_list是C語(yǔ)言中解決變參問(wèn)題的一組宏
- (BOOL)executeUpdate:(NSString*)sql withVAList: (va_list)args;
關(guān)于va_list盔夜,在后續(xù)更新中會(huì)提到堤魁,這里先不做過(guò)多闡述
批處理:可以通過(guò)調(diào)用executeStatements方法妥泉,一次執(zhí)行多個(gè)sql語(yǔ)句
API:
- (BOOL)executeStatements:(NSString *)sql;
- (BOOL)executeStatements:(NSString *)sql withResultBlock:(__attribute__((noescape)) FMDBExecuteStatementsCallbackBlock _Nullable)block;
例:
NSString *creatSqlString = @"CREATE TABLE IF NOT EXISTS grouptable(id INTEGER, gcid VARCHAR(64), gcname VARCHAR(64));"
@"CREATE TABLE IF NOT EXISTS usertable(id INTEGER, name VARCHAT(1024), age INTEGER)";
[_dataBaseQueue inDatabase:^(FMDatabase *db) {
[db open];
[db executeStatements:creatSqlString];
[db close];
}];
事物:(詳情見:三、關(guān)于事物的使用)
- (BOOL)beginTransaction;
- (BOOL)beginDeferredTransaction;
- (BOOL)beginImmediateTransaction;
- (BOOL)beginExclusiveTransaction;
- (BOOL)commit;
- (BOOL)rollback;
三蝇率、關(guān)于事物的使用
在數(shù)據(jù)庫(kù)中刽沾,事務(wù)可以保證數(shù)據(jù)操作的完整性。當(dāng)存在大量并發(fā)操作锅尘,容易出現(xiàn)死鎖問(wèn)題布蔗。在SQLite中忙菠,為了解決該問(wèn)題牛欢,提供三種事務(wù)模式
3.1淆游、事物模式
- Exclusive
- Deferred
- Immediate
typedef NS_ENUM(NSInteger, FMDBTransaction) {
// 事務(wù)開始執(zhí)行,就獲取EXCLUSIVE鎖拾稳,此時(shí)腊脱,其他連接無(wú)法進(jìn)行任何讀寫操作
FMDBTransactionExclusive,
// 事務(wù)開始執(zhí)行時(shí),不預(yù)先獲取任何鎖悍抑。當(dāng)進(jìn)行讀操作杜耙,獲取SHARED LOCK鎖佑女;當(dāng)進(jìn)行第一次寫操作,獲取RESERVED鎖
FMDBTransactionDeferred,
// 事務(wù)開始執(zhí)行摸吠,就獲取RESERVED鎖嚎花。這時(shí),其他連接只能進(jìn)行讀操作
FMDBTransactionImmediate,
};
3.2轿腺、延時(shí)性事務(wù)和獨(dú)占性事務(wù)的區(qū)別:
在SQLite 3.0.8或更高版本中丛楚,事務(wù)可以是延遲的,即時(shí)的或者獨(dú)占的仿荆。
“延遲的”即是說(shuō)在數(shù)據(jù)庫(kù)第一次被訪問(wèn)之前不獲得鎖。 這樣就會(huì)延遲事務(wù)锦亦,BEGIN語(yǔ)句本身不做任何事情令境。直到初次讀取或訪問(wèn)數(shù)據(jù)庫(kù)時(shí)才獲取鎖舔庶。對(duì)數(shù)據(jù)庫(kù)的初次讀取創(chuàng)建一個(gè)SHARED鎖 ,初次寫入創(chuàng)建一個(gè)RESERVED鎖惕橙。由于鎖的獲取被延遲到第一次需要時(shí)弥鹦,別的線程或進(jìn)程可以在當(dāng)前線程執(zhí)行BEGIN語(yǔ)句之后創(chuàng)建另外的事務(wù) 寫入數(shù)據(jù)庫(kù)。
若事務(wù)是即時(shí)的朦促,則執(zhí)行BEGIN命令后立即獲取RESERVED鎖苍鲜,而不等數(shù)據(jù)庫(kù)被使用玷犹。在執(zhí)行BEGIN IMMEDIATE之后歹颓, 你可以確保其它的線程或進(jìn)程不能寫入數(shù)據(jù)庫(kù)或執(zhí)行BEGIN IMMEDIATE或BEGIN EXCLUSIVE. 但其它進(jìn)程可以讀取數(shù)據(jù)庫(kù)。 獨(dú)占事務(wù)在所有的數(shù)據(jù)庫(kù)獲取EXCLUSIVE鎖领跛,在執(zhí)行BEGIN EXCLUSIVE之后撤奸,你可以確保在當(dāng)前事務(wù)結(jié)束前沒(méi)有任何其它線程或進(jìn)程 能夠讀寫數(shù)據(jù)庫(kù)
API
- (void)inTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block
- (void)inDeferredTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block
- (void)inExclusiveTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block
- (void)inImmediateTransaction:(__attribute__((noescape)) void (^)(FMDatabase * _Nonnull, BOOL * _Nonnull))block
當(dāng)我們直接調(diào)用inTransaction胧瓜、beginTransaction操作時(shí),使用EXCLUSIVE模式蒲肋,適合數(shù)據(jù)庫(kù)讀寫較少的情況;
當(dāng)使用beginDefferedTransaction方法兜粘,則使用DEFFERED模式孔轴,適合讀寫頻繁的場(chǎng)景
3.3、FMDB中事物使用的兩種方式
- 直接開啟事物
- 在inData中開啟事物
[_dataBaseQueue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
[db open];
BOOL isDeleteGroupSuccess = [db executeUpdate:@"DELETE FROM grouptable WHERE gcid = ?", groupID];
BOOL isDeleteMembershipSuccess = [db executeUpdate:@"DELETE FROM groupshiptable WHERE gcid = ?", groupID];
if (!isDeleteGroupSuccess || !isDeleteMembershipSuccess) {
// 當(dāng)對(duì)兩個(gè)表的操作中玄窝,其中一個(gè)失敗悍引,數(shù)據(jù)回滾
*rollback = YES;
return;
}
[db close];
}];
[_dataBaseQueue inDatabase:^(FMDatabase * _Nonnull db) {
[db open];
// 開啟事物
[db beginTransaction];
BOOL isDeleteGroupSuccess = [db executeUpdate:@"DELETE FROM grouptable WHERE gcid = ?", groupID];
BOOL isDeleteMembershipSuccess = [db executeUpdate:@"DELETE FROM groupshiptable WHERE gcid = ?", groupID];
if (!isDeleteGroupSuccess || !isDeleteMembershipSuccess) {
// 當(dāng)對(duì)兩個(gè)表的操作中趣斤,其中一個(gè)失敗,數(shù)據(jù)回滾
[db rollback];
return;
}
// 提交事物
[db commit];
[db close];
}];
看過(guò)原碼的人應(yīng)該都知道玉凯,其實(shí)他們是一樣的联贩,inTransaction默認(rèn)調(diào)用事物的FMDBTransactionExclusive類型,
switch (transaction) {
case FMDBTransactionExclusive:
// inTransaction其實(shí)就是直接調(diào)用 ?? ??
[[self database] beginTransaction];
break;
case FMDBTransactionDeferred:
[[self database] beginDeferredTransaction];
break;
case FMDBTransactionImmediate:
[[self database] beginImmediateTransaction];
break;
}
四盲厌、數(shù)據(jù)庫(kù)升級(jí)常見的三種方式
- 記錄版本號(hào)方式
- 根據(jù)新增字段是否存在方式
- 表遷移方式
4.1吗浩、版本號(hào)方式
廢話少說(shuō)没隘,直接上代碼
// 1、靜態(tài)寫死當(dāng)前版本號(hào)阀湿,手動(dòng)更新
static const NSInteger kCurrentDBVersion = 1;
- (void)checkDataBaseUpgrade {
// 2瑰妄、獲取舊的版本號(hào)翰撑,與當(dāng)前版本號(hào)對(duì)比
NSString *oldDBVersion = [[NSUserDefaults standardUserDefaults] objectForKey:dbVersionKey];
if ([oldDBVersion integerValue] < kCurrentDBVersion) {
// 3啊央、如果本次存儲(chǔ)的版本號(hào) < 當(dāng)前版本號(hào)涨醋,數(shù)據(jù)庫(kù)升級(jí)浴骂,更新本地存儲(chǔ)
[self upgrade:[oldDBVersion integerValue]];
[[NSUserDefaults standardUserDefaults] setObject:@(kCurrentDBVersion) forKey:dbVersionKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
// 4、遞歸的方式更新
- (void)upgrade:(NSInteger)oldVersion {
if (oldVersion >= kCurrentDBVersion) {
return;
}
switch (oldVersion) {
case 1: { // version1 -> version2
[self upgradeFromFirstToSecond];
}
break;
case 2: { // version2 -> version3
[self upgradeFromSecondToThird];
}
break;
// ...
default:
break;
}
oldVersion ++;
[self upgrade:oldVersion];
}
// 5趣苏、在需要添加的表格中增加字段
- (void)upgradeFromFirstToSecond {
// ALTER TABLE table_name ADD column_name datatype
}
- (void)upgradeFromSecondToThird {
// ALTER TABLE table_name ADD column_name datatype
}
4.2梯轻、檢測(cè)字段是否存在方式
在FMDatabaseAdditions類中調(diào)用columnExists方法用于檢測(cè)喳挑,傳入需要檢測(cè)的字段名和表名
- (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName;
具體操作為:
+ (void)checkDataBaseUpdate {
FMDatabaseQueue *dataBaseQueue = [FMDatabaseQueue databaseQueueWithPath:DATABASEPATH];
[dataBaseQueue inDatabase:^(FMDatabase *db) {
[db open];
if ([db columnExists:@"relation" inTableWithName:@"Profile"]) {
// 如果Profile表中存在relation這個(gè)字段,不需要操作
} else {
// 如果Profile表中不存在relation這個(gè)字段单绑,插入列
[db executeUpdate:@"ALTER TABLE Profile ADD relation text"];
}
[db close];
}];
}
4.3曹宴、表遷移
第三種方式比較復(fù)雜笛坦,忘了當(dāng)時(shí)在哪里看到了,講的邏輯就是
- 重新創(chuàng)建一個(gè)表
- 將舊表的數(shù)據(jù)導(dǎo)入到新的表中
- 刪除舊表
作為一個(gè)拓展吧蜗帜,以后有時(shí)間的話再研究资厉,這里提供一個(gè)思路