从C#到Objective-C,循序渐进学习苹果开发(7)--使用FMDB对Sqlite数据库进行操作
本随笔系列主要介绍从一个Windows平台从事C#开发到Mac平台苹果开发的一系列感想和体验历程,本系列文章是在起步阶段逐步积累的,希望带给大家更好,更真实的转换历程体验。本篇主要开始介绍基于XCode进行IOS程序的开发,介绍使用FMDB对Sqlite数据库进行操作,以及对数据库操作类进行抽象设计,以期达到重用、简化、高效开发的目的。
在.NET领域开发了很多年,一般常见的项目都需要操作数据库,包括有Oracle、SqlServer、Mysql、Sqlite、Access等数据库,这些数据库是很常见的,我们在.NET环境里面开发的各种系统,可能都或多或少需要和其中一种以上的数据库打交道,这也是我致力于提炼我的.NET领域的Winform开发框架、Web开发框架、混合式开发框架的目的,尽可能达到简化、重用、高效开发的目的。
虽然现在在IOS领域做一些研究开发,即使IOS设备更多强调的是一个多媒体的设备,但是数据库的操作还是必不可少,因此我先从我熟悉的数据库这块入手,了解其中数据库是如何操作的,有哪些现成的组件进行参考学习等等。
在IOS里面开发,提起和数据库打交道,可能很多人都熟悉FMDB这个数据库的组件,它对IOS里面操作Sqlite数据库进行了很大程度的简化,简化后,我们大多数情况下,只需要和
FMDatabase和FMResultSet打交道即可,使用起来非常方便。
1、FMDB的操作
为了较好介绍整体性的内容,我们先从FMDB的各种操作进行介绍。
1)数据库打开操作
FMDatabase *db=[FMDatabase databaseWithPath:dbPath] ;if (![db open]) {
NSLog(@"无法打开数据库");return;
}
2)数据库操作executeUpdate
使用FMDB,对于没有返回记录的操作,都可以用executeUpdate进行操作,如下是删除记录的函数
- (BOOL) deleteByCondition:(NSString *) condition {
NSString*query = [NSString stringWithFormat:@"Delete FROM %@ Where %@", self.tableName, condition];
BOOL result=[self.database executeUpdate:query];returnresult;
}
当然,对于SQLite很多数据库操作,我们可以使用参数化语句进行操作,如下例子所示
[db executeUpdate:@"INSERT INTO User (Name,Age) VALUES (?,?)",@"张三",[NSNumber numberWithInt:30]]
参数化也可以使用 : 字符作为参数标识,如下所示,是我封装的一个数据库操作函数
- (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];returnresult;
}
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集合进行遍历使用,我们可以根据Resultset的一些方法获取到不同的数据内容。
相对于FMResult的操作方式,原生态的Sqlite数据库操作代码如下所示。看完是不是感觉使用FMDB类库操作数据库方便很多呢。
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方法有下面几种,分别用于获取不同的数据:
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnIndex:
objectForColumn:
2、数据库操作层的设计
上面小节介绍了使用FMDB类库对Sqlite数据库的操作,使我们大致了解了在IOS里面对数据库操作的过程。但是单纯如果介绍这些,我觉得太泛泛了,而且对我们使用起来也还是很不方便,很多时候,我总是想通过设计的方式,来简化我的各种操作处理。如我们知道,IOS里面的Objective C也提供了很多高级语言都有的属性,如对象继承,接口、多态等等。
我很早之前写过的随笔《
Winform开发框架之数据访问层的设计
》 ,介绍了我在.NET领域里面的数据库访问层的设计,由于这种设计,能很大程度上减少代码量,并提高开发效率,因此,我也想再IOS里面形成这样的数据访问设计,虽然IOS里面可能主要是使用单机版的数据库,如SQLite数据库,所以我想简化一些.NET里面的设计模型。
在.NET里面,我的框架分层主要如下所示,这种框架的设计模式,已经很好应用在了我的Winform开发框架、WCF及混合式开发框架、Web框架里面。它们常见的分层模式,可以分为UI层、BLL层、DAL层、IDAL层、Entity层、公用类库层等等
而其中的DAL层的设计,示意图如下所示,DAL层主要是通过继承自BaseDAL基类(如BaseDALSQL)进行, BaseDALSQL进行更高一层的抽象,已达到更好的应用目的。
而我们在IOS里面,则可以主要考虑SQLite数据库即可,因此,我把设计通过简化,构造下面的设计模型
在项目里面,数据访问层的文件如下所示(为了演示测试方便,使用User表进行操作)。
为了实现数据的承载,我们需要把表的数据转换为实体类进行显示和操作,因此实体类的设计也需要考虑好。由于数据访问层的基类,封装了大多数的数据操作,也包括返回的数据对象和集合,因此数据访问层的基类,也涉及到了数据类型返回的问题。
由于IOS里面的Objective C里面没有泛型这样的东西,因此有两种方式可以来实现基类实体类的处理:一是使用动态类型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的继承关系差不多了。
对于数据访问层的设计代码,它就是在数据访问基类的定义如下,基类使用了FMDB的数据访问类进行操作的,里面很多操作的接口就是模仿我在.NET领域里面的数据访问层的设计。
// //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 数据库相关属性@property (nonatomic, strong) NSString*pathToDatabase;
@property (nonatomic,readonly) FMDatabase *database; //数据库操作对象 @property (nonatomic, strong) NSString *tableName;//表名称 @property (nonatomic, strong) NSString *primaryKey;//主键 @property (nonatomic, strong) NSString *sortField;//排序,默认为主键 @property (nonatomic, assign, getter=isDescending) BOOL descending;//是否降序查询//将DataReader的属性值转化为实体类的属性值,返回实体类(子类必须重写) -(id) rsToEntity:(FMResultSet *)rs;//将实体对象的属性值转化为字典列表对应的键值(子类必须重写) -(NSDictionary *) dictByEntity:(BaseEntity *) info;#pragma 基础操作接口 //根据指定对象的ID,从数据库中删除指定对象 -(BOOL) deleteById:(id) key;//根据指定条件,从数据库中删除指定对象 - (BOOL) deleteByCondition:(NSString *) condition;//更新对象属性到数据库中 -(BOOL) update:(BaseEntity *) info byKey:(id) key;//插入指定对象到数据库中 -(BOOL) insert:(BaseEntity *) info;//插入指定对象到数据库中,并返回最后插入的ID -(NSInteger) insert2:(BaseEntity *) info;//查询数据库,检查是否存在指定ID的对象 - (BaseEntity *) findById:(id) key;//根据条件查询数据库,如果存在返回第一个对象 -(BaseEntity *) findSingle:(NSString *) condition;//根据条件查询数据库,如果存在返回第一个对象 -(BaseEntity *) findSingle:(NSString *) condition orderBy:(NSString *) orderBy;//根据条件查询数据库,并返回对象集合 - (NSArray *) find:(NSString *) condition;//根据条件查询数据库,并返回对象集合 - (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy;//获取表的全部数据 - (NSArray *) getAll;//获取表的全部数据 - (NSArray *) getAll:(NSString *) orderBy;//获取某字段数据字典列表 -(NSArray *) getFieldList:(NSString *) fieldName;//获取表的所有记录数量 -(int) getRecordCount;//根据条件,获取表查询的记录数量 -(int) getRecordCount:(NSString *) condition;//根据条件,判断是否存在记录 -(BOOL) isExistRecord:(NSString *)condition;//查询数据库,检查是否存在指定键值的对象 -(BOOL) isExist:(NSString *)fieldName value:(id) value;//根据主键和字段名称,获取对应字段的内容 -(NSString *) getFieldValue:(NSString *)key fieldName:(NSString *)fieldName;//执行SQL查询语句,返回查询结果的所有记录的第一个字段,用逗号分隔。 -(NSString *) sqlValueList:(NSString *)query;#pragma 数据库初始化函数及关闭操作 //根据SQLite数据库地址初始化数据库 -(id) initWithPath:(NSString *)filePath;//根据SQLite数据库名称初始化数据库 -(id) initWithFileName:(NSString *)fileName;//关闭连接 -(void) close;@end
和我的.NET框架里面的数据访问层设计一样,数据访问基类已经封装了大多数的数据访问操作,因此各个表的数据访问对象,它的代码就可以很简洁了。从上面的基类接口设计可以看到,里面一些实体类返回函数或者列表返回函数,都使用了BaseEntity作为对象,我们具体在起子类使用的时候,把它返回的对象再一次转换即可。对于数据库访问基类,我们以一个返回集合的接口实现来分析。
- (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];returnarray;
}
上面代码使用了参数化的SQL语句进行查询,并且,对返回的数据库的ResultSet进行转换为实体类。
info = [self rsToEntity:rs];
由于基类封装了大多数的数据库操作函数,因此数据访问层的具体表的实现类,可以很简洁,但是已经具备了常见的CRUD操作,以及一些分页查询等复杂的数据操作功能。
// //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
基于篇幅的原因,我将在下一篇介绍如何在界面层中使用这样的数据访问设计类,先放上一些测试程序的界面截图。