FMDB 使用進階

資料庫.jpg
目錄:
- 序
- 類檔案解讀
- 更多你不知道的API
- 關於事物的使用
- 資料庫升級
序
只要是擼過一遍FMDB的人,基本都知道怎麼用,增、刪、改、查,偶爾涉及多表操作,多條件查詢,搜搜sql語句,基本上沒什麼問題,但是你以為這就夠了麼?我曾經也是這樣以為的。
簡單問一下:
1、除了執行、更新,你還用到FMDB中的哪些API(例如:批處理)
2、FMDB中事物的程式碼實現方法?
3、資料庫升級是怎麼操作的?
4、va_list是什麼東西?
如果你全都知道,那麼可以略過這篇文章,如果你和我當初一樣,只知道封裝個增、刪、改、查方法,我建議最好還是讀一下
一、類檔案
檔名 | 描述 |
---|---|
FMDatabase | 一個FMDatabase物件就代表一個單獨的SQLite資料庫 |
FMResultSet | 使用FMDatabase執行查詢後的結果集合 |
FMDatabaseQueue | 用於在多執行緒中執行多個查詢或更新,它是執行緒安全的 |
FMDatabaseAdditions | 新增對查詢結果只返回單個值的方法進行簡化,對錶、列是否存在,版本號,校驗SQL等等功能 |
二、更多你不知道的API
很長時間以來我只知道這兩個方法,一個更新,一個執行,夠用啦
db executeQuery:<#(nonnull NSString *), ...#> db executeUpdate:<#(nonnull NSString *), ...#>
- 更新(create、drop、insert、update、delete)
- 執行(select)
直到有一天沒事兒的時候看了看原始碼,才發現,雖然這個庫很輕量級,但是我看到的依舊是冰山一角,接下來讓我看看它還有哪些能讓我眼前一亮的東西。
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、該方法的引數必須是物件,不能為基礎資料型別,否則就會崩潰 - (BOOL)executeUpdate:(NSString*)sql, ...; 錯誤寫法::x: [_dataBaseQueue inDatabase:^(FMDatabase * _Nonnull db) { [db executeUpdate:@"INSERT INTO usertable VALUES (?, ? , ?)", 1, @"lizhiqiang", 25]; }]; 2、如果需要插入基礎資料型別,要麼自己做一下轉換,要麼呼叫以下方法 - (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、這個沒什麼可說的,陣列引數,直接上程式碼 - (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指標引數,記錄更新失敗 - (BOOL)executeUpdate:(NSString*)sql values:(NSArray * _Nullable)values error:(NSError * _Nullable __autoreleasing *)error; 5、注意,這個mark一下,引數為字典,寫法變了,並且插入欄位必須與字典key相對應 - (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語言中解決變參問題的一組巨集 - (BOOL)executeUpdate:(NSString*)sql withVAList: (va_list)args; 關於va_list,在後續更新中會提到,這裡先不做過多闡述
批處理:可以通過呼叫executeStatements方法,一次執行多個sql語句
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]; }];
事物:(詳情見:三、關於事物的使用)
- (BOOL)beginTransaction; - (BOOL)beginDeferredTransaction; - (BOOL)beginImmediateTransaction; - (BOOL)beginExclusiveTransaction; - (BOOL)commit; - (BOOL)rollback;
三、關於事物的使用
在資料庫中,事務可以保證資料操作的完整性。當存在大量併發操作,容易出現死鎖問題。在SQLite中,為了解決該問題,提供三種事務模式
3.1、事物模式
- Exclusive
- Deferred
- Immediate
typedef NS_ENUM(NSInteger, FMDBTransaction) { // 事務開始執行,就獲取EXCLUSIVE鎖,此時,其他連線無法進行任何讀寫操作 FMDBTransactionExclusive, // 事務開始執行時,不預先獲取任何鎖。當進行讀操作,獲取SHARED LOCK鎖;當進行第一次寫操作,獲取RESERVED鎖 FMDBTransactionDeferred, // 事務開始執行,就獲取RESERVED鎖。這時,其他連線只能進行讀操作 FMDBTransactionImmediate, };
3.2、延時性事務和獨佔性事務的區別:
在SQLite 3.0.8或更高版本中,事務可以是延遲的,即時的或者獨佔的。
“延遲的”即是說在資料庫第一次被訪問之前不獲得鎖。 這樣就會延遲事務,BEGIN語句本身不做任何事情。直到初次讀取或訪問資料庫時才獲取鎖。對資料庫的初次讀取建立一個SHARED鎖 ,初次寫入建立一個RESERVED鎖。由於鎖的獲取被延遲到第一次需要時,別的執行緒或程序可以在當前執行緒執行BEGIN語句之後建立另外的事務 寫入資料庫。
若事務是即時的,則執行BEGIN命令後立即獲取RESERVED鎖,而不等資料庫被使用。在執行BEGIN IMMEDIATE之後, 你可以確保其它的執行緒或程序不能寫入資料庫或執行BEGIN IMMEDIATE或BEGIN EXCLUSIVE. 但其它程序可以讀取資料庫。 獨佔事務在所有的資料庫獲取EXCLUSIVE鎖,在執行BEGIN EXCLUSIVE之後,你可以確保在當前事務結束前沒有任何其它執行緒或程序 能夠讀寫資料庫
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
當我們直接呼叫inTransaction、beginTransaction操作時,使用EXCLUSIVE模式,適合資料庫讀寫較少的情況;
當使用beginDefferedTransaction方法,則使用DEFFERED模式,適合讀寫頻繁的場景
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) { // 當對兩個表的操作中,其中一個失敗,資料回滾 *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) { // 當對兩個表的操作中,其中一個失敗,資料回滾 [db rollback]; return; } // 提交事物 [db commit]; [db close]; }];
看過原碼的人應該都知道,其實他們是一樣的,inTransaction預設呼叫事物的FMDBTransactionExclusive型別,
switch (transaction) { case FMDBTransactionExclusive: // inTransaction其實就是直接呼叫:sparkles::sparkles: :sparkles::sparkles: [[self database] beginTransaction]; break; case FMDBTransactionDeferred: [[self database] beginDeferredTransaction]; break; case FMDBTransactionImmediate: [[self database] beginImmediateTransaction]; break; }
四、資料庫升級常見的三種方式
- 記錄版本號方式
- 根據新增欄位是否存在方式
- 表遷移方式
4.1、版本號方式
廢話少說,直接上程式碼
// 1、靜態寫死當前版本號,手動更新 static const NSInteger kCurrentDBVersion = 1; - (void)checkDataBaseUpgrade { // 2、獲取舊的版本號,與當前版本號對比 NSString *oldDBVersion = [[NSUserDefaults standardUserDefaults] objectForKey:dbVersionKey]; if ([oldDBVersion integerValue] < kCurrentDBVersion) { // 3、如果本次儲存的版本號 < 當前版本號,資料庫升級,更新本地儲存 [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、檢測欄位是否存在方式
在FMDatabaseAdditions類中呼叫columnExists方法用於檢測,傳入需要檢測的欄位名和表名
- (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這個欄位,不需要操作 } else { // 如果Profile表中不存在relation這個欄位,插入列 [db executeUpdate:@"ALTER TABLE Profile ADD relation text"]; } [db close]; }]; }
4.3、表遷移
第三種方式比較複雜,忘了當時在哪裡看到了,講的邏輯就是
- 重新建立一個表
- 將舊錶的資料匯入到新的表中
- 刪除舊錶
作為一個拓展吧,以後有時間的話再研究,這裡提供一個思路