实现数据本地化的步骤
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@H_403_178@作为该单例的一个属性,可以看做是一个单例。你会问为什么要做成单例呢?(后边具体说)
@property@H_403_178@(nonatomic,retain,readonly@H_403_178@)@H_403_178@FMDatabase*db;//@H_403_178@数据库
@H_403_178@+ (sqliteHelper *)sharedsqliteHelper
@H_403_178@{
@H_403_178@ static sqliteHelper *sharedsqliteHelper;
@H_403_178@
@H_403_178@ @synchronized(self)
@H_403_178@ {
@H_403_178@ if(!sharedsqliteHelper)
@H_403_178@sharedsqliteHelper = [[sqliteHelper alloc] init];
@H_403_178@ returnsharedsqliteHelper;
@H_403_178@ }
@H_403_178@}
@H_403_178@
@H_403_178@- (id)init
@H_403_178@{
@H_403_178@ self = [super init];
@H_403_178@ if (self) {
@H_403_178@ //@H_403_178@初始化数据库
@H_403_178@ if ([self initializeDb]) {
@H_403_178@ db = [FMDatabase databaseWithPath:dbFilePath];
@H_403_178@ };
@H_403_178@ }
@H_403_178@ return self;
@H_403_178@}
@H_403_178@下边这段代码涉及到应用版本更新时@H_403_178@且@H_403_178@数据库表结构发生改变的情况,咱们后边再说。
}- (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层。
上部分截图:
@H_403_178@@interface PatientGroupService : NSObject
@H_403_178@/************************************************************
@H_403_178@@H_403_178@方法名称:@H_403_178@+ (NSArray *) getAllGroupsWithDoctorId:(NSString*)doctorId;
@H_403_178@@H_403_178@方法描述:获取医生对应的分组列表
@H_403_178@@H_403_178@参@H_403_178@@H_403_178@数:@H_403_178@doctorId @H_403_178@:医生@H_403_178@ id
@H_403_178@@H_403_178@返回值:医生对应的分组列表对象的@H_403_178@数组
@H_403_178@***********************************************************/
@H_403_178@+ (NSArray *)getAllGroupsWithDoctorId:(NSString *)doctorId;
@H_403_178@/************************************************************
@H_403_178@@H_403_178@方法名称:@H_403_178@+ (BOOL)addGroupWithDoctorId:(PatientGroupModel *)model;
@H_403_178@@H_403_178@方法描述:添加一个新分组
@H_403_178@@H_403_178@参@H_403_178@@H_403_178@数:@H_403_178@model @H_403_178@:分组对象
@H_403_178@@H_403_178@返回值:添加是否成功@H_403_178@ yes@H_403_178@为成功@H_403_178@ no@H_403_178@为失败
@H_403_178@***********************************************************/
@H_403_178@+ (BOOL) addGroup:(PatientGroupModel *)model;
@H_403_178@@implementation PatientService
@H_403_178@+(BOOL)addPatient:(PatientModel *)patient
@H_403_178@{
@H_403_178@ BOOL result=false;
@H_403_178@
@H_403_178@ //@H_403_178@获取数据库对象,因为所以程序都用一个数据库@H_403_178@所以都从@H_403_178@sqliteHelper@H_403_178@中取用
@H_403_178@ FMDatabase *db=[sqliteHelper sharedsqliteHelper].db;
@H_403_178@ 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
@H_403_178@ @try {
@H_403_178@ //@H_403_178@打开数据库
@H_403_178@ if ([db open]) {
@H_403_178@ //@H_403_178@执行插入语句获得@H_403_178@执行结果
@H_403_178@ 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]];
@H_403_178@ }
@H_403_178@ }
@H_403_178@ @catch (NSException *exception) {
@H_403_178@ NSLog(@"@H_403_178@添加病人出错:@H_403_178@%@",exception);
@H_403_178@ }
@H_403_178@ @finally {
@H_403_178@ //@H_403_178@使用完毕之后@H_403_178@一定要关闭数据库
@H_403_178@ [db close];
@H_403_178@ }
@H_403_178@ return result;
@H_403_178@}
@H_403_178@+ (NSArray *)getAllGroupsWithDoctorId:(NSString*)doctorId{
@H_403_178@ //@H_403_178@获取数据库对象,因为所以程序都用一个数据库@H_403_178@所以都从@H_403_178@sqliteHelper@H_403_178@中取用
@H_403_178@ FMDatabase *db=[sqliteHelper sharedsqliteHelper].db;
@H_403_178@ NSString *sql=@"selectgroup_id,group_name,operator,sort,timestamp fromt_patient_group where doctor_id = ? order by create_time ";
@H_403_178@ NSMutableArray *array=[NSMutableArray array];
@H_403_178@ @try {
@H_403_178@ //@H_403_178@打开数据库
@H_403_178@ if ([db open]) {
@H_403_178@ //@H_403_178@执行查询语句获得@H_403_178@执行结果
@H_403_178@ FMResultSet *resultset = [db executeQuery:sqlwithArgumentsInArray:@[doctorId]];
@H_403_178@ //@H_403_178@循环@H_403_178@Resultset@H_403_178@获取值
@H_403_178@ while ([resultset next]) {
@H_403_178@ PatientGroupModel *model=[[PatientGroupModelalloc]init];
@H_403_178@ model.group_id=[resultset stringForColumn:@"group_id"];
@H_403_178@ model.group_name=[resultset stringForColumn:@"group_name"];
@H_403_178@ model.create_time=[resultset stringForColumn:@"create_time"];
@H_403_178@ model.groupOperator=[resultset stringForColumn:@"operator"];
@H_403_178@ model.sort=[NSNumber numberWithInt:[resultsetintForColumn:@"sort"]];
@H_403_178@ model.doctor_id=[resultset stringForColumn:@"doctor_id"];
@H_403_178@ model.isSelected = NO;
@H_403_178@ model.timestasp=[resultset stringForColumn:@"timestamp"];
@H_403_178@ //@H_403_178@将对象添加到@H_403_178@array
@H_403_178@ [array addObject:model];
@H_403_178@ }
@H_403_178@ }
@H_403_178@ }
@H_403_178@ @catch (NSException *exception) {
@H_403_178@ NSLog(@"@H_403_178@查询病人分组出错:@H_403_178@%@",exception);
@H_403_178@ }
@H_403_178@ @finally {
@H_403_178@ //@H_403_178@使用完毕之后@H_403_178@一定要关闭数据库
@H_403_178@ [db close];
@H_403_178@ }
@H_403_178@ return array;
@H_403_178@}
@H_403_178@+ (BOOL) addGroup:(PatientGroupModel*)model{
@H_403_178@ BOOL result=false;
@H_403_178@
@H_403_178@ //@H_403_178@获取数据库对象,因为所以程序都用一个数据库@H_403_178@所以都从@H_403_178@sqliteHelper@H_403_178@中取用
@H_403_178@ FMDatabase *db=[sqliteHelper sharedsqliteHelper].db;
@H_403_178@ NSString *sql=@"insert intot_patient_group(group_id,is_selected,timestamp)values(?,?)";
@H_403_178@ @try {
@H_403_178@ //@H_403_178@打开数据库
@H_403_178@ if ([db open]) {
@H_403_178@ //@H_403_178@执行插入语句获得@H_403_178@执行结果
@H_403_178@ 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]];
@H_403_178@ }
@H_403_178@ }
@H_403_178@ @catch (NSException *exception) {
@H_403_178@ NSLog(@"@H_403_178@添加病人分组出错:@H_403_178@%@",exception);
@H_403_178@ }
@H_403_178@ @finally {
@H_403_178@ //@H_403_178@使用完毕之后@H_403_178@一定要关闭数据库
@H_403_178@ [db close];
@H_403_178@ }
@H_403_178@ return result;
@H_403_178@ }
}上边的代码中? 和 @H_403_178@ @[]@H_403_178@里的参数对应,多个参数用,隔开
@H_403_178@FMResultSet *resultset =[db executeQuery:sql withArgumentsInArray:@[]]
当然也可以用@H_403_178@stringWithFormat拼接
@H_403_178@NSString *l = [NSStringstringWithFormat:@"selectgroup_id,timestampfrom t_patient_group where doctor_id = %@ order by create_time",doctorId];
@H_403_178@FMResultSet *resultset =[db executeQuery:l]@H_403_178@;
@H_403_178@程序中调用如下@H_403_178@:
@H_403_178@[PatientGroupService addGroup:groupModel];
@H_403_178@NSArray *arr = [PatientGroupServicegetAllGroupsWithDoctorId:@””];
@H_403_178@注@H_403_178@1@H_403_178@:为什么要使用单例
@H_403_178@实际操作中你可能会出现以下的两个错误@H_403_178@:
问题一:"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实例中执行。
这里我选用了第一种解决方法。
@H_403_178@注@H_403_178@2@H_403_178@:数据库版本
@H_403_178@随着软件的迭代,数据库结构也会发生改变,怎么样才能在小版本更新(数据库结构不变的情况下)尽量少的清空数据的数据。
@H_403_178@定义一个全局宏@H_403_178@db_version
@H_403_178@在@H_403_178@sqlite@H_403_178@文件里建立一个配置表
@H_403_178@这里就有了文章开头说到的数据库版本控制,仅当数据库结构发生变化时才把该@H_403_178@sqlite@H_403_178@文件删除,其他情况下只对数据库里的记录修改就可以了,节省了流量。
@H_403_178@注意:数据库版本和程序的版本是不一定是相同的,仅当数据库结构发生变化时,才修改宏的值,而且必须修改宏的值,否则会使程序的数据结构错误,照成崩溃或者数据无法写入的情况。
@H_403_178@注意@H_403_178@3@H_403_178@:向数据库里写数据时记得先清楚旧的记录,否则会因为主键相同插入数据库失败,导致数据不更新的情况
@H_403_178@
@H_403_178@注@H_403_178@4@H_403_178@:利用时间戳来进一步节省流量(实际使用中有可能涉及到关系表,有些麻烦)
数据量较大的应用,一般都会在本地建一个小型的数据库,将服务端的一部分数据放在本地进行,以提升用户体验,但是问题来了,以朋友表为例,若数据量很大的话,上万条记录,你不能每次都把所有的数据都网络获取一遍(浪费流量,你会等待很长时间),此时你可以通过时间戳来解决数据的同步问题。@H_403_178@
假设医生有a和b两个客户端,a客户端要和b客户端进行数据同步@H_403_178@
解决方案如下:@H_403_178@
在朋友表中加入时间戳字段(服务器维护时间戳字段,本地不做修改,只是进行保存,服务端每条记录变动时,都会更新该记录的时间戳),每次请求网络数据时,发送朋友表中得最大时间戳,服务端接收到该时间戳之后,只是将大于该时间戳的记录返回给客户端(首次进入的时候时,发送一个最小的时间戳),这样就极大的降低了传输的数据量@H_403_178@
分析以下几种情况:@H_403_178@
1.a客户端在1点增加了一个患者,b在1点后更新数据(上一次更新早于1点)获取到朋友表中最大得时间戳,该时间戳小于a增加的那条记录的时间戳,这样服务器将返回a加入的这条记录,将它插入b的本地数据库即可@H_403_178@
2.a客户端在1点更新了一个患者的信息,b在1点后更新数据(上一次更新早于1点)获取到朋友表中最大得时间戳,该时间戳小于a增加的那条记录的时间戳,这样服务器将返回a更新的这条记录,将原记录删除,插入新的记录即可@H_403_178@
3.a客户端在1点了删除了一个患者(假删除,将记录状态改为删除状态),b在1点后更新数据(上一次更新早于1点)获取到朋友表中最大得时间戳,该时间戳小于a增加的那条记录的时间戳,这样服务器将返回a更新的这条记录,将原记录删除,插入新的记录即可(查询患者时过滤调状态为删除的部分)
@H_403_178@
总上,在获取到记录后,根据记录的主键,先删除该条记录(若为删除记录则终止),如果该记录的状态不为删除的话,再重新插入数据库即可,这样就实现了大量数据的同步