实现数据本地化的步骤
1..设计模型
根据实际的业务需求,设计出model层。这个就不用多说了,直接上代码。
@property(strong,nonatomic)NSString *group_id;//分组Id
@property(strong,nonatomic)NSString*group_name;//分组名称
@property(strong,nonatomic)NSString*groupOperator;//操作人
@property(strong,nonatomic)NSString*create_time;//创建时间
@property(strong,nonatomic)NSString*doctor_id;//医生id
@property(strong,nonatomic)NSNumber*isSelected;
@property(strong,nonatomic)NSNumber*sort;//序号
@property(strong,nonatomic)NSString*timestasp;//序号
创建数据库文件一般有两种方法,一是通过数据库管理工具创建数据库文件,创建、设计表。我用的是sqlitemanger,也可以用其他的工具如火狐插件等,大同小异。
另一种方式是用代码创建sqlite文件,创建设计表。其实上边的工具就是封装了这些代码,实现了数据库的设计,免去了写数据库设计代码的过程。
这里我用的是第一种方法。
3.将创建的sqlite文件从bundle拷贝到沙盒的doument下,这里我用到了第三方库fmda。这里为了方便,把对数据库文件的操作封装到sqliteHelper文件里。这里我把sqlitHelper作为一个单列,把FMDatabase作为该单例的一个属性,可以看做是一个单例。你会问为什么要做成单例呢?(后边具体说)
@property(nonatomic,retain,readonly)FMDatabase*db;//数据库
+ (sqliteHelper *)sharedsqliteHelper
{
static sqliteHelper *sharedsqliteHelper;
@synchronized(self)
{
if(!sharedsqliteHelper)
sharedsqliteHelper = [[sqliteHelper alloc] init];
returnsharedsqliteHelper;
}
}
- (id)init
{
self = [super init];
if (self) {
//初始化数据库
if ([self initializeDb]) {
db = [FMDatabase databaseWithPath:dbFilePath];
};
}
return self;
}
下边这段代码涉及到应用版本更新时且数据库表结构发生改变的情况,咱们后边再说。
}- (BOOL) initializeDb {
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentFolderPath =[searchPaths objectAtIndex: 0];
//查看文件目录
NSLog(@"%@",documentFolderPath);
dbFilePath = [documentFolderPathstringByAppendingPathComponent:@"e_doctor_db.sqlite"];
//END:code.DatabaseShoppingList.findDocumentsDirectory
//START:code.DatabaseShoppingList.copyDatabaseFileToDocuments
//数据库文件是否存在Documents路径先 不存在则复制,存在则核对版本
if (! [[NSFileManagerdefaultManager] fileExistsAtPath: dbFilePath]) {
// didn't find db,need to copy
NSString *backupDbPath =[[NSBundle mainBundle] pathForResource:@"e_doctor_db"ofType:@"sqlite"];
if (backupDbPath == nil) {
// couldn't find backup db tocopy,bail
return NO;
}
else {
BOOL copiedBackupDb =[[NSFileManager defaultManager] copyItemAtPath:backupDbPath toPath:dbFilePatherror:nil];
if (! copiedBackupDb) {
// copying backup db Failed,bail
return NO;
}
else
{
[self updateDBVersion];
}
}
}
else
{
//存在则核对版本 检查是否有更新
NSString *dbverson=[selfgetCurrentDBVersion];
if ([dbversondoubleValue]!=api_ios_db_verson) {
//版本号不一致
if ([[NSFileManagerdefaultManager] removeItemAtPath:dbFilePath error:nil]) {
NSString *backupDbPath =[[NSBundle mainBundle] pathForResource:@"e_doctor_db"ofType:@"sqlite"];
if (backupDbPath == nil) {
// couldn't find backup db tocopy,bail
return NO;
}
else {
BOOL copiedBackupDb =[[NSFileManager defaultManager] copyItemAtPath:backupDbPath toPath:dbFilePatherror:nil];
if (! copiedBackupDb) {
// copying backup db Failed,bail
return NO;
}
else
{
[self updateDBVersion];
}
}
}
else
{
return NO;
}
}
}
return YES;
}
3.咱们开始写dao层。
上部分截图:
@interface PatientGroupService : NSObject
/************************************************************
方法名称:+ (NSArray *) getAllGroupsWithDoctorId:(NSString*)doctorId;
参数:doctorId :医生 id
返回值:医生对应的分组列表对象的数组
***********************************************************/
+ (NSArray *)getAllGroupsWithDoctorId:(NSString *)doctorId;
/************************************************************
方法名称:+ (BOOL)addGroupWithDoctorId:(PatientGroupModel *)model;
参数:model :分组对象
返回值:添加是否成功 yes为成功 no为失败
***********************************************************/
+ (BOOL) addGroup:(PatientGroupModel *)model;
@implementation PatientService
+(BOOL)addPatient:(PatientModel *)patient
{
BOOL result=false;
//获取数据库对象,因为所以程序都用一个数据库所以都从sqliteHelper中取用
FMDatabase *db=[sqliteHelper sharedsqliteHelper].db;
NSString *sql=@"insert into t_patient(patient_id,telephone,name,sex,birthday,address,remark,message_number,message_updatetime,create_time,timestamp,group_id,doctor_id,allergic_history,status,certificate,medicare_card,socialsecurity_card)values(?,?,?)";//18
@try {
//打开数据库
if ([db open]) {
//执行插入语句获得执行结果
result = [db executeUpdate:sql withArgumentsInArray:@[patient.pId,patient.phoneNum,patient.name,patient.sex,patient.birthDay,patient.address,[CommonHelperisBlankString:patient.headimge_url],patient.messageNum,patient.messageUpdatetime,patient.creatTime,patient.timestamp,patient.groupId,patient.doctorId,patient.allergicHistory,patient.status,patient.certificate,patient.medicare_card,patient.socialsecurity_card]];
}
}
@catch (NSException *exception) {
NSLog(@"添加病人出错:%@",exception);
}
@finally {
[db close];
}
return result;
}
+ (NSArray *)getAllGroupsWithDoctorId:(NSString*)doctorId{
//获取数据库对象,因为所以程序都用一个数据库所以都从sqliteHelper中取用
FMDatabase *db=[sqliteHelper sharedsqliteHelper].db;
NSString *sql=@"selectgroup_id,group_name,operator,sort,timestamp fromt_patient_group where doctor_id = ? order by create_time ";
NSMutableArray *array=[NSMutableArray array];
@try {
//打开数据库
if ([db open]) {
//执行查询语句获得执行结果
FMResultSet *resultset = [db executeQuery:sqlwithArgumentsInArray:@[doctorId]];
//循环Resultset获取值
while ([resultset next]) {
PatientGroupModel *model=[[PatientGroupModelalloc]init];
model.group_id=[resultset stringForColumn:@"group_id"];
model.group_name=[resultset stringForColumn:@"group_name"];
model.create_time=[resultset stringForColumn:@"create_time"];
model.groupOperator=[resultset stringForColumn:@"operator"];
model.sort=[NSNumber numberWithInt:[resultsetintForColumn:@"sort"]];
model.doctor_id=[resultset stringForColumn:@"doctor_id"];
model.isSelected = NO;
model.timestasp=[resultset stringForColumn:@"timestamp"];
//将对象添加到array
[array addObject:model];
}
}
}
@catch (NSException *exception) {
NSLog(@"查询病人分组出错:%@",exception);
}
@finally {
[db close];
}
return array;
}
+ (BOOL) addGroup:(PatientGroupModel*)model{
BOOL result=false;
//获取数据库对象,因为所以程序都用一个数据库所以都从sqliteHelper中取用
FMDatabase *db=[sqliteHelper sharedsqliteHelper].db;
NSString *sql=@"insert intot_patient_group(group_id,is_selected,timestamp)values(?,?)";
@try {
//打开数据库
if ([db open]) {
//执行插入语句获得执行结果
result = [db executeUpdate:sql withArgumentsInArray:@[model.group_id,model.group_name,model.create_time,model.groupOperator,model.sort,model.doctor_id,[NSNumbernumberWithBool:NO],model.timestasp]];
}
}
@catch (NSException *exception) {
NSLog(@"添加病人分组出错:%@",exception);
}
@finally {
[db close];
}
return result;
}
}上边的代码中? 和 @[]里的参数对应,多个参数用,隔开
FMResultSet *resultset =[db executeQuery:sql withArgumentsInArray:@[]]
当然也可以用stringWithFormat拼接
NSString *l = [NSStringstringWithFormat:@"selectgroup_id,timestampfrom t_patient_group where doctor_id = %@ order by create_time",doctorId];
上边边的执行查询改为
FMResultSet *resultset =[db executeQuery:l];
程序中调用如下:
[PatientGroupService addGroup:groupModel];
NSArray *arr = [PatientGroupServicegetAllGroupsWithDoctorId:@””];
注1:为什么要使用单例
实际操作中你可能会出现以下的两个错误:
问题一:"is currently in use" 出现的场景是这样的,多线程操作数据库,每个线程都使用了FMDatabase实例(注意没有使用FMDatabaseQueue)。
问 题二:“database is locked"出现的场景是这样的,多线程操作数据库,每个线程各自创建了FMDatabaseQueue实例操作数据库,或者一个线程创建 FMDatabaseQueue实例来操作,而另外的线程创建了FMDatabase实例来操作。
原 因:FMDB多线程操作数据库,必须使用FMDatabaseQueue,而且必须只创建一个实例,也就是多个线程操作数据库的是同一个FMDatabaseQueue实例。首先FMDatabase是不具备线程安全的,如果两个线程中同时操作数据库,就会"iscurrently in use" ;FMDatabasequeue其实是一个调度队列(G-C-D),数据库的操作必须是顺序执行,不能两个数据库的操作同时执行,如果是两个线程各自创 建了FMDatabaseQueue的实例,线程同时执行时,就会出现相同的数据库操作同时触发,导致”database islocked“,所以必须是一个FMDatabaseQueue实例下,多个线程下同时操作,其实是在排在同一个队列中逐一操作的,没有同时操作。
注意:FMDatabaseQueue其实是在排在同一个队列中逐一操作的,并没有提高执行效率。我选用的是第一种方法,你也可以使用FMDatabaseQueue,验证下是否能够提高执行效率。
1. FMDatabase 作为单例,同步进行数据库操作。
2.只创建一个FMDatabaseQueue实例,所有的FMDatabase的实例方法都在这个FMDatabaseQueue实例中执行。
这里我选用了第一种解决方法。
注2:数据库版本
随着软件的迭代,数据库结构也会发生改变,怎么样才能在小版本更新(数据库结构不变的情况下)尽量少的清空数据的数据。
定义一个全局宏db_version
这里就有了文章开头说到的数据库版本控制,仅当数据库结构发生变化时才把该sqlite文件删除,其他情况下只对数据库里的记录修改就可以了,节省了流量。
注意:数据库版本和程序的版本是不一定是相同的,仅当数据库结构发生变化时,才修改宏的值,而且必须修改宏的值,否则会使程序的数据结构错误,照成崩溃或者数据无法写入的情况。
注意3:向数据库里写数据时记得先清楚旧的记录,否则会因为主键相同插入数据库失败,导致数据不更新的情况
注4:利用时间戳来进一步节省流量(实际使用中有可能涉及到关系表,有些麻烦)
数据量较大的应用,一般都会在本地建一个小型的数据库,将服务端的一部分数据放在本地进行,以提升用户体验,但是问题来了,以朋友表为例,若数据量很大的话,上万条记录,你不能每次都把所有的数据都网络获取一遍(浪费流量,你会等待很长时间),此时你可以通过时间戳来解决数据的同步问题。
假设医生有a和b两个客户端,a客户端要和b客户端进行数据同步
解决方案如下:
在朋友表中加入时间戳字段(服务器维护时间戳字段,本地不做修改,只是进行保存,服务端每条记录变动时,都会更新该记录的时间戳),每次请求网络数据时,发送朋友表中得最大时间戳,服务端接收到该时间戳之后,只是将大于该时间戳的记录返回给客户端(首次进入的时候时,发送一个最小的时间戳),这样就极大的降低了传输的数据量
分析以下几种情况:
1.a客户端在1点增加了一个患者,b在1点后更新数据(上一次更新早于1点)获取到朋友表中最大得时间戳,该时间戳小于a增加的那条记录的时间戳,这样服务器将返回a加入的这条记录,将它插入b的本地数据库即可
2.a客户端在1点更新了一个患者的信息,b在1点后更新数据(上一次更新早于1点)获取到朋友表中最大得时间戳,该时间戳小于a增加的那条记录的时间戳,这样服务器将返回a更新的这条记录,将原记录删除,插入新的记录即可
3.a客户端在1点了删除了一个患者(假删除,将记录状态改为删除状态),b在1点后更新数据(上一次更新早于1点)获取到朋友表中最大得时间戳,该时间戳小于a增加的那条记录的时间戳,这样服务器将返回a更新的这条记录,将原记录删除,插入新的记录即可(查询患者时过滤调状态为删除的部分)
总上,在获取到记录后,根据记录的主键,先删除该条记录(若为删除记录则终止),如果该记录的状态不为删除的话,再重新插入数据库即可,这样就实现了大量数据的同步