本文主要是介紹iOS上資料庫儲存方案,利用ORM對FMDB做一層封裝,讓程式碼更加簡潔方便。
一、資料庫開發的蛋疼問題
有了FMDB對sqlite 的封裝,iOS端的資料庫操作相當方便,我們再也不用面對那些磨人的執行緒問題和蛋疼的sqlite。但是在實際開發中仍無法避免的遇到一些蛋疼問題。
1 2 3 4 5 6 7 8 9 10 11 12 |
- (void)createPaintTable{ [self.db executeUpdate:@"CREATE TABLE paint_table (paintID varchar(32), answercount int, create_timestamp double, last_modified_time double, imgwidth int, imgheight int, imgurl varchar(256), last_reply_user_id varchar(32), text varchar(512), money int, questype int, replytype int, user_id varchar(32), viewcount int, commentcount int, supportcount int, author_id varchar(32), replyAuthor_id varchar(32), accepted_user_id varchar(32))"]; [self checkError]; } |
這是簡單的建立一張表,我想你看到這樣的程式碼都要發瘋。然而更讓人發瘋的是某天因為產品的需求我們需要增加一個欄位,那我們不得不在在各個地方改啊改。而一旦因為疏忽資料庫升級沒有處理好,就可能造成很嚴重的線上事故。(很遺憾我就遇到了這種事故)
二、如何解決這種問題
2.1 Key-Value式的儲存 - YTKKeyValueStore
iOS客戶端的資料表往往還要對應一個Model,這導致在使用時還要加層Model轉化,而移動端對於資料庫的查詢要求並不高,針對這些特點Key-Value式的儲存應運而生。比較代表性的框架有 猿題庫的 YTKKeyValueStore(主要思路是將資料模型進行序列化做為Value儲存)。但是這類框架的缺陷也相當明顯,只能通過主Key查詢。而且如果模型發生變化,資料升級處理也只能放在 模型轉換時處理。Key-Value式的儲存實際上已經不能稱之為資料庫儲存了,它是根據客戶端的特殊需求而產生的一種特殊產物,資料庫對它而言只是一種容器。
2.2 ORM框架-GYDataCenter
ORM(Object Relational Mapping)框架採用後設資料來描述物件一關係對映細節,後設資料一般採用XML格式,並且存放在專門的物件一對映檔案中。只要提供了持久化類與表的對映關係,ORM框架在執行時就能參照對映檔案的資訊,把物件持久化到資料庫中。當前ORM框架主要有四種:Hibernate(Nhibernate),iBATIS,mybatis,EclipseLink。
以上是百度百科對ORM框架的解釋,大家注意我標註的兩個關鍵詞,執行時以及物件,iOS客戶端的資料庫儲存本質就是要將一個物件儲存到資料庫,而OC也正是一種執行時語言,我們要做的就是建立一個對映檔案,在執行時參照對映檔案產生對應sql語句將物件儲存到資料庫。
MLeaksFinder這個框架我想不少同學都不陌生,它採用了一種很巧妙的方式,讓我們在DEBUG模式下可以輕鬆的發現記憶體洩漏問題。今天我逛作者 Zepo 的gitHub時發現他又寫了另外一個框架GYDataCenter我簡單的看了一下它正是採用ORM思想寫的一款iOS客戶端的資料庫儲存方案,與我的想法不謀而合。
我並沒有看GYDataCenter的原始碼,只是根據使用方式提出一些問題。GYDataCenter的 對映關係 是通過GYModelObject提供相應方法來提供,而使用必需繼承GYModelObject實現對應方法。也就是說作者將 對映關係 與 物件模型 合在了GYModelObject類,造成了耦合。這種耦合會造成一些問題,在專案中 模型使用的地方太多,模型轉換,view展示,資料處理都要涉及到模型將 對映關係 加到模型中很容易導致模型的臃腫。而繼承關係的使用也使得入侵性太強。所以說 對映關係 應該獨立於物件 模型存在。
三、JYDatabase(鋪墊了那麼多這才是我要講的……)
JYDatabase 這是本人基於ORM思想寫的一款資料庫應用框架,和GYDataCenter一樣也是基於FMDB實現的,下面主要講一下框架的實現和使用。
3.1 要解決的問題
1 2 |
1.自動處理資料庫升級,讓使用者不用考慮資料庫升級帶來煩勞。 2.封裝簡單常用的查詢語句,讓使用者只用關注特殊的SQL查詢,不用被蛋疼的sql語句折磨。 |
3.2 資料表的建立(對映關係的建立)
資料表的建立需要繼承 JYContentTable(該類實現了工作中用到的大部分SQL查詢),只要重寫以下幾個方法就可以快速建立一張資料表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
// 必須實現 contentClass 是該表所對應的模型類,tableName 是表的名字 - (void)configTableName{ self.contentClass = [JYPersonInfo class]; self.tableName = @"JYPersonTable"; } // 必須實現 contentId 是該表的主鍵(也是唯一索引)比如使用者的userId 必須是 contentClass 的屬性 - (NSString *)contentId{ return @"personnumber"; } // 資料表的其他欄位,必須是 contentClass 的屬性,如不實現則預設取 contentClass 以“DB”結尾 的屬性 - (NSArray *)getContentField{ return @[@"mutableString1",@"integer1",@"uInteger1",@"int1",@"bool1",@"double1"]; } // 表建立時對應欄位的預設長度,如不寫,取預設。 - (NSDictionary*)fieldLenght{ return @[@"mutableString1":@"512"]; } // 查詢是否使用NSCache快取,預設YES。 - (BOOL)enableCache{ return NO; } 注意:1.資料表對映的屬性支援 NSString NSMutableString NSInteger NSUInteger int BOOL double float NSData 等資料型別,具體如下: NSDictionary * jy_correspondingDic(){ return @{@"Tb":@"BOOL", @"TB":@"BOOL", @"Tc":@"BOOL", @"TC":@"BOOL", @"Td":@"DOUBLE", @"TD":@"DOUBLE", @"Tf":@"FLOAT", @"TF":@"INTEGER", @"Ti":@"INTEGER", @"TI":@"INTEGER", @"Tq":@"INTEGER", @"TQ":@"INTEGER", @"T@\"NSMutableString\"":@"VARCHAR", @"T@\"NSString\"":@"VARCHAR", @"T@\"NSData\"":@"BLOB", @"T@\"UIImage\"":@"BLOB", @"T@\"NSNumber\"":@"BLOB", @"T@\"NSDictionary\"":@"BLOB", @"T@\"NSMutableDictionary\"":@"BLOB", @"T@\"NSMutableArray\"":@"BLOB", @"T@\"NSArray\"":@"BLOB",}; } 2.NSCache的預設快取條數是20條,可自行設定修改self.cache.countLimit = 20; 使用enableCache 將優先從快取中取資料 如自行實現的查詢請在適當情況下使用以下三個方法來加入快取。方法內部有 enableCache 的實現。 - (id)getCacheContentID:(NSString *)aID; - (void)saveCacheContent:(id)aContent; - (void)removeCacheContentID:(NSString *)aID; |
3.3資料庫的建立和升級管理
3.3.1資料庫的建立和升級管理類需要繼承JYDataBase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
關鍵方法: // 該方法會根據當前版本判斷 是建立資料庫表還是 資料表升級 - (void)buildWithPath:(NSString *)aPath mode:(ArtDatabaseMode)aMode; // 返回當前資料庫版本,只要資料表有修改 返回版本號請 +1 預設返回 1 - (NSInteger)getCurrentDBVersion{ return 4; } // 所有資料表的建立請在該方法實現 呼叫固定方法 - (void)createTable:(FMDatabase *)aDB; - (void)createAllTable:(FMDatabase *)aDB{ [self.personTable createTable:aDB]; } // 所有資料表的升級請在該方法實現 呼叫固定方法 - (void)insertDefaultData:(FMDatabase *)aDb; - (void)updateDB:(FMDatabase *)aDB{ [self.personTable updateDB:aDB]; } |
3.3.2資料庫升級的實現- (void)updateDB:(FMDatabase *)aDB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
對於每一張表,所謂的修改無非是 新增了一個 新的欄位 或減少了幾個欄位(這種情況很少)。 1. 欄位的對比 PRAGMA table_info([tableName]) 可以獲取一張表的所有欄位 再與當前需要的欄位做對比即可得到要增加的欄位和減少的欄位 2. 欄位的新增 sqlite有提供對應的SQL語句實現 ALTER TABLE tableName ADD 欄位 type(lenght) 3. 減少欄位 sqlite並未提供對應的SQL語句實現但可通過以下方法實現 a.根據原表新建一個表 sql = [NSString stringWithFormat:@"create table %@ as select %@%@ from %@", tempTableName,[self contentId],tableField,self.tableName]; b.刪除原表 sql = [NSString stringWithFormat:@"drop table if exists %@", self.tableName]; c.將表改名 sql = [NSString stringWithFormat:@"alter table %@ rename to %@",tempTableName ,self.tableName]; d.為新表新增唯一索引 sql = [NSString stringWithFormat:@"create unique index '%@_key' on %@(%@)", self.tableName,self.tableName,[self contentId]]; |
3.4條件查詢的實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
關於複雜查詢我提供了一個簡單的方法 - (NSArray *)getContentByConditions:(void (^)(JYQueryConditions *make))block; 可以看下它的使用 NSArray*infos = [[JYDBService shared] getPersonInfoByConditions:^(JYQueryConditions *make) { make.field(@"personnumber").greaterThanOrEqualTo(@"12345620"); make.field(@"bool1").equalTo(@"1"); make.field(@"personnumber").lessTo(@"12345630"); make.asc(@"bool1").desc(@"int1"); }]; 其實它不過產生了如下一條查詢語句: SELECT * FROM JYPersonTable WHERE personnumber >= 12345620 AND bool1 = 1 AND personnumber = 12345620 實現實際相當簡單,先用 JYQueryConditions 記錄下所描描述的引數,最後再拼接出完整的sql語句。 至於通過點語法的鏈式呼叫則參考了 Masonry 的宣告方式 - (JYQueryConditions * (^)(NSString *compare))equalTo; - (JYQueryConditions * (^)(NSString *field))field{ return ^id(NSString *field) { NSMutableDictionary *dicM = [[NSMutableDictionary alloc] init]; dicM[kField] = field; [self.conditions addObject:dicM]; return self; }; |
}
四、結尾
關於JYDatabase的使用和介紹 大家可以去gitHub檢視更詳細的說明
https://github.com/weijingyunIOS/JYDatabase,關於上面介紹的框架大家可以根據實際需要做下對比後選擇使用。 GYDataCenter 是相當不錯的資料庫處理方案,如果它早點出來或者我早點看到也許就不會花費大量精力去寫JYDatabase了,不過寫JYDatabase也是很愉快的因為它的思路走向是根據我自身遇到的痛點來寫的,比如sql查詢,就是因為sql語句可能就是因為一個關鍵位置的空格問題導致無法執行,所以我專門寫了個JYQueryConditions來弱化sql。