本隨筆系列主要介紹從一個Windows平臺從事C#開發(fā)到Mac平臺蘋果開發(fā)的一系列感想和體驗歷程,本系列文章是在起步階段逐步積累的,希望帶給大家更好,更真實的轉(zhuǎn)換歷程體驗。本篇主要開始介紹基于XCode進行IOS程序的開發(fā),介紹使用FMDB對Sqlite數(shù)據(jù)庫進行操作,以及對數(shù)據(jù)庫操作類進行抽象設(shè)計,以期達到重用、簡化、高效開發(fā)的目的。
在.NET領(lǐng)域開發(fā)了很多年,一般常見的項目都需要操作數(shù)據(jù)庫,包括有Oracle、SqlServer、Mysql、Sqlite、Access等數(shù)據(jù)庫,這些數(shù)據(jù)庫是很常見的,我們在.NET環(huán)境里面開發(fā)的各種系統(tǒng),可能都或多或少需要和其中一種以上的數(shù)據(jù)庫打交道,這也是我致力于提煉我的.NET領(lǐng)域的Winform開發(fā)框架、Web開發(fā)框架、混合式開發(fā)框架的目的,盡可能達到簡化、重用、高效開發(fā)的目的。
雖然現(xiàn)在在IOS領(lǐng)域做一些研究開發(fā),即使IOS設(shè)備更多強調(diào)的是一個多媒體的設(shè)備,但是數(shù)據(jù)庫的操作還是必不可少,因此我先從我熟悉的數(shù)據(jù)庫這塊入手,了解其中數(shù)據(jù)庫是如何操作的,有哪些現(xiàn)成的組件進行參考學(xué)習(xí)等等。
在IOS里面開發(fā),提起和數(shù)據(jù)庫打交道,可能很多人都熟悉FMDB這個數(shù)據(jù)庫的組件,它對IOS里面操作Sqlite數(shù)據(jù)庫進行了很大程度的簡化,簡化后,我們大多數(shù)情況下,只需要和FMDatabase和FMResultSet打交道即可,使用起來非常方便。
1、FMDB的操作
為了較好介紹整體性的內(nèi)容,我們先從FMDB的各種操作進行介紹。
1)數(shù)據(jù)庫打開操作
FMDatabase *db= [FMDatabase databaseWithPath:dbPath] ;
if (![db open]) {
NSLog(@"無法打開數(shù)據(jù)庫");
return ;
}
2)數(shù)據(jù)庫操作executeUpdate
使用FMDB,對于沒有返回記錄的操作,都可以用executeUpdate進行操作,如下是刪除記錄的函數(shù)
- (BOOL) deleteByCondition:(NSString *) condition {
NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ ", self.tableName, condition];
BOOL result = [self.database executeUpdate:query];
return result;
}
當然,對于SQLite很多數(shù)據(jù)庫操作,我們可以使用參數(shù)化語句進行操作,如下例子所示
[db executeUpdate:@"INSERT INTO User (Name,Age) VALUES (?,?)",@"張三",[NSNumber numberWithInt:30]]
參數(shù)化也可以使用 : 字符作為參數(shù)標識,如下所示,是我封裝的一個數(shù)據(jù)庫操作函數(shù)
- (BOOL) deleteById:(id) key
{
NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:key, @"id", nil];
NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ =:id", self.tableName, self.primaryKey];
BOOL result = [self.database executeUpdate:query withParameterDictionary:argsDict];
return result;
}
3)返回集合的操作
操作有返回集合的語句,我們就需要用到FMResultSet對象了,這個對象類似于以前見過的游標,不過不一樣的東西而已。
FMResultSet *rs=[db executeQuery:@"SELECT * FROM User"];
rs=[db executeQuery:@"SELECT * FROM User WHERE Age = ?",@"20"];
while ([rs next]){
NSLog(@"%@ %@",[rs stringForColumn:@"Name"],[rs stringForColumn:@"Age"]);
}
上面的代碼操作,返回一個Resultset集合進行遍歷使用,我們可以根據(jù)Resultset的一些方法獲取到不同的數(shù)據(jù)內(nèi)容。
相對于FMResult的操作方式,原生態(tài)的Sqlite數(shù)據(jù)庫操作代碼如下所示??赐晔遣皇歉杏X使用FMDB類庫操作數(shù)據(jù)庫方便很多呢。
NSString *sqlQuery = @"SELECT * FROM User";
sqlite3_stmt * statement;
if (sqlite3_prepare_v2(db, [sqlQuery UTF8String], -1, &statement, nil) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
char *name = (char*)sqlite3_column_text(statement, 1);
NSString *nsNameStr = [[NSString alloc]initWithUTF8String:name];
int age = sqlite3_column_int(statement, 2);
char *address = (char*)sqlite3_column_text(statement, 3);
NSString *nsAddressStr = [[NSString alloc]initWithUTF8String:address];
NSLog(@"name:%@ age:%d address:%@",nsNameStr,age, nsAddressStr);
}
}
sqlite3_close(db);
FMResultSet方法有下面幾種,分別用于獲取不同的數(shù)據(jù):
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnIndex:
objectForColumn:
2、數(shù)據(jù)庫操作層的設(shè)計
上面小節(jié)介紹了使用FMDB類庫對Sqlite數(shù)據(jù)庫的操作,使我們大致了解了在IOS里面對數(shù)據(jù)庫操作的過程。但是單純?nèi)绻榻B這些,我覺得太泛泛了,而且對我們使用起來也還是很不方便,很多時候,我總是想通過設(shè)計的方式,來簡化我的各種操作處理。如我們知道,IOS里面的Objective C也提供了很多高級語言都有的屬性,如對象繼承,接口、多態(tài)等等。
我很早之前寫過的隨筆《Winform開發(fā)框架之數(shù)據(jù)訪問層的設(shè)計》 ,介紹了我在.NET領(lǐng)域里面的數(shù)據(jù)庫訪問層的設(shè)計,由于這種設(shè)計,能很大程度上減少代碼量,并提高開發(fā)效率,因此,我也想再IOS里面形成這樣的數(shù)據(jù)訪問設(shè)計,雖然IOS里面可能主要是使用單機版的數(shù)據(jù)庫,如SQLite數(shù)據(jù)庫,所以我想簡化一些.NET里面的設(shè)計模型。
在.NET里面,我的框架分層主要如下所示,這種框架的設(shè)計模式,已經(jīng)很好應(yīng)用在了我的Winform開發(fā)框架、WCF及混合式開發(fā)框架、Web框架里面。它們常見的分層模式,可以分為UI層、BLL層、DAL層、IDAL層、Entity層、公用類庫層等等

而其中的DAL層的設(shè)計,示意圖如下所示,DAL層主要是通過繼承自BaseDAL基類(如BaseDALSQL)進行, BaseDALSQL進行更高一層的抽象,已達到更好的應(yīng)用目的。

而我們在IOS里面,則可以主要考慮SQLite數(shù)據(jù)庫即可,因此,我把設(shè)計通過簡化,構(gòu)造下面的設(shè)計模型

在項目里面,數(shù)據(jù)訪問層的文件如下所示(為了演示測試方便,使用User表進行操作)。

為了實現(xiàn)數(shù)據(jù)的承載,我們需要把表的數(shù)據(jù)轉(zhuǎn)換為實體類進行顯示和操作,因此實體類的設(shè)計也需要考慮好。由于數(shù)據(jù)訪問層的基類,封裝了大多數(shù)的數(shù)據(jù)操作,也包括返回的數(shù)據(jù)對象和集合,因此數(shù)據(jù)訪問層的基類,也涉及到了數(shù)據(jù)類型返回的問題。
由于IOS里面的Objective C里面沒有泛型這樣的東西,因此有兩種方式可以來實現(xiàn)基類實體類的處理:一是使用動態(tài)類型id類型作為實體類類型,一種是使用一種半類型化的類型(實體類的基類)作為對象,我傾向于使用后者,因為畢竟比較接近真實的類型了。
//
// BaseEntity.h
// MyDatabaseProject
//
// Created by 伍華聰 on 14-4-2.
// Copyright (c) 2014年 伍華聰. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface BaseEntity : NSObject
@end
//
// UserInfo.h
// MyDatabaseProject
//
// Created by 伍華聰 on 14-3-30.
// Copyright (c) 2014年 伍華聰. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BaseEntity.h"
@interface UserInfo : BaseEntity
@property(nonatomic, copy) NSString *ID;
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *fullName;
@end
這種繼承模式和.NET的繼承關(guān)系差不多了。
對于數(shù)據(jù)訪問層的設(shè)計代碼,它就是在數(shù)據(jù)訪問基類的定義如下,基類使用了FMDB的數(shù)據(jù)訪問類進行操作的,里面很多操作的接口就是模仿我在.NET領(lǐng)域里面的數(shù)據(jù)訪問層的設(shè)計。
//
// BaseDAL.h
// MyDatabaseProject
//
// Created by 伍華聰 on 14-3-30.
// Copyright (c) 2014年 伍華聰. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BaseEntity.h"
@interface BaseDAL : NSObject
{
NSString *pathToDatabase;
}
#pragma 數(shù)據(jù)庫相關(guān)屬性
@property (nonatomic, strong) NSString *pathToDatabase;
@property (nonatomic, readonly) FMDatabase *database; // 數(shù)據(jù)庫操作對象
@property (nonatomic, strong) NSString *tableName;//表名稱
@property (nonatomic, strong) NSString *primaryKey;//主鍵
@property (nonatomic, strong) NSString *sortField;//排序,默認為主鍵
@property (nonatomic, assign, getter=isDescending) BOOL descending;//是否降序查詢
//將DataReader的屬性值轉(zhuǎn)化為實體類的屬性值,返回實體類(子類必須重寫)
-(id) rsToEntity:(FMResultSet *)rs;
//將實體對象的屬性值轉(zhuǎn)化為字典列表對應(yīng)的鍵值(子類必須重寫)
-(NSDictionary *) dictByEntity:(BaseEntity *) info;
#pragma 基礎(chǔ)操作接口
//根據(jù)指定對象的ID,從數(shù)據(jù)庫中刪除指定對象
- (BOOL) deleteById:(id) key;
//根據(jù)指定條件,從數(shù)據(jù)庫中刪除指定對象
- (BOOL) deleteByCondition:(NSString *) condition;
//更新對象屬性到數(shù)據(jù)庫中
-(BOOL) update:(BaseEntity *) info byKey:(id) key;
//插入指定對象到數(shù)據(jù)庫中
-(BOOL) insert:(BaseEntity *) info;
//插入指定對象到數(shù)據(jù)庫中,并返回最后插入的ID
-(NSInteger) insert2:(BaseEntity *) info;
//查詢數(shù)據(jù)庫,檢查是否存在指定ID的對象
- (BaseEntity *) findById:(id) key;
//根據(jù)條件查詢數(shù)據(jù)庫,如果存在返回第一個對象
-(BaseEntity *) findSingle:(NSString *) condition;
//根據(jù)條件查詢數(shù)據(jù)庫,如果存在返回第一個對象
-(BaseEntity *) findSingle:(NSString *) condition orderBy:(NSString *) orderBy;
//根據(jù)條件查詢數(shù)據(jù)庫,并返回對象集合
- (NSArray *) find:(NSString *) condition;
//根據(jù)條件查詢數(shù)據(jù)庫,并返回對象集合
- (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy;
//獲取表的全部數(shù)據(jù)
- (NSArray *) getAll;
//獲取表的全部數(shù)據(jù)
- (NSArray *) getAll:(NSString *) orderBy;
//獲取某字段數(shù)據(jù)字典列表
-(NSArray *) getFieldList:(NSString *) fieldName;
//獲取表的所有記錄數(shù)量
-(int) getRecordCount;
//根據(jù)條件,獲取表查詢的記錄數(shù)量
-(int) getRecordCount:(NSString *) condition;
//根據(jù)條件,判斷是否存在記錄
-(BOOL) isExistRecord:(NSString *)condition;
//查詢數(shù)據(jù)庫,檢查是否存在指定鍵值的對象
-(BOOL) isExist:(NSString *)fieldName value:(id) value;
//根據(jù)主鍵和字段名稱,獲取對應(yīng)字段的內(nèi)容
-(NSString *) getFieldValue:(NSString *)key fieldName:(NSString *)fieldName;
//執(zhí)行SQL查詢語句,返回查詢結(jié)果的所有記錄的第一個字段,用逗號分隔。
-(NSString *) sqlValueList:(NSString *)query;
#pragma 數(shù)據(jù)庫初始化函數(shù)及關(guān)閉操作
//根據(jù)SQLite數(shù)據(jù)庫地址初始化數(shù)據(jù)庫
-(id) initWithPath:(NSString *)filePath;
//根據(jù)SQLite數(shù)據(jù)庫名稱初始化數(shù)據(jù)庫
-(id) initWithFileName:(NSString *)fileName;
// 關(guān)閉連接
-(void) close;
@end
和我的.NET框架里面的數(shù)據(jù)訪問層設(shè)計一樣,數(shù)據(jù)訪問基類已經(jīng)封裝了大多數(shù)的數(shù)據(jù)訪問操作,因此各個表的數(shù)據(jù)訪問對象,它的代碼就可以很簡潔了。從上面的基類接口設(shè)計可以看到,里面一些實體類返回函數(shù)或者列表返回函數(shù),都使用了BaseEntity作為對象,我們具體在起子類使用的時候,把它返回的對象再一次轉(zhuǎn)換即可。對于數(shù)據(jù)庫訪問基類,我們以一個返回集合的接口實現(xiàn)來分析。
- (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy {
NSString *query = [NSString stringWithFormat:@"SELECT * FROM %@ ", self.tableName];
if (condition != nil) {
query = [query stringByAppendingFormat:@" where %@ ", condition];
}
if (orderBy != nil) {
query = [query stringByAppendingString:orderBy];
}
else {
query = [query stringByAppendingFormat:@" ORDER BY %@ %@ ", self.sortField, self.isDescending ? @"DESC" : @"ASC"];
}
FMResultSet *rs = [self.database executeQuery:query];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:[rs columnCount]];
BaseEntity *info = nil; //默認初始化為空
while ([rs next]) {
info = [self rsToEntity:rs];
[array addObject:info];
}
[rs close];
return array;
}
上面代碼使用了參數(shù)化的SQL語句進行查詢,并且,對返回的數(shù)據(jù)庫的ResultSet進行轉(zhuǎn)換為實體類。
info = [self rsToEntity:rs];
由于基類封裝了大多數(shù)的數(shù)據(jù)庫操作函數(shù),因此數(shù)據(jù)訪問層的具體表的實現(xiàn)類,可以很簡潔,但是已經(jīng)具備了常見的CRUD操作,以及一些分頁查詢等復(fù)雜的數(shù)據(jù)操作功能。
//
// UserDAL.h
// MyDatabaseProject
//
// Created by 伍華聰 on 14-3-30.
// Copyright (c) 2014年 伍華聰. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BaseDAL.h"
#import "UserInfo.h"
@interface UserDAL : BaseDAL
{
}
//單例模式
+(UserDAL *) defaultDAL;
@end
基于篇幅的原因,我將在下一篇介紹如何在界面層中使用這樣的數(shù)據(jù)訪問設(shè)計類,先放上一些測試程序的界面截圖。




