数据库版本迁移顾名思义就是在原有的数据库中更新数据库,数据库中的数据保持不变对表的增、删、该、查。
数据持久化存储:
plist文件(属性列表)
preference(偏好设置)
NSKeyedArchiver(归档)
SQLite 3
CoreData
这几种方式,我就不介绍其他的了,你可以自己去查询其功能用处,主要SQLite 3对数据的存储。
一、手动更新迭代
由于开发需要需要对消息进行存储,第三方的数据库局限所以我们公司要求对聊天记录、聊天对像、好友进行查询(说白了就是和微信搜索做的一模一样就好了)。
首先你要明白一点为了避免在不通的线程中对相同的数据库进行操作fmdb已经对数据库加锁了。我在数据库中使用了单例,保证工程全局都可以使用。
+ (MyFMDB *)sharedMyFMDB {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
在本篇文章中先说一下大致的数据库迁移的思路,在数据库中存储一个记录版本号的表
Version表。本地写一个记录工程需要不要更新的版本静态变量
#define kCurrentSqliteVersion 0;发布新版本的时候就更改本地的数据库版本号 0 ->1 然后从数据库中取出数据库中的版本号0。
if (oldSqliteVer < kCurrentSqliteVersion) {//sqlite版本小于当前要创建的版本,需要更新
}
依次类推
[self upgrade:oldSqliteVer]; //更新数据库内容
[self insertSqliteVersion:kCurrentSqliteVersion]; //版本号更新需要放在更新数据库内容之后,在没有版本号的数据库版本中,需要在upgrade的地方去创建version表
然后用递归的方式更新
- (void)upgrade:(NSInteger)oldVersion {
if (oldVersion >= kCurrentSqliteVersion) {
return;
}
switch (oldVersion) {
case 0:
[self upgradeFrom0To1];
break;
case 1: //从1版本升级到2版本
[self upgradeFrom1To2];
break;
case 2: //版本拓展:以后若有增加则持续增加
[self upgradeFrom2To3];
break;
case 3: //版本拓展:以后若有增加则持续增加
[self upgradeFrom3To4];
break;
default:
break;
}
oldVersion ++;
// 递归判断是否需要升级:保证老版本从最低升级到当前
[self upgrade:oldVersion];
}
举个例子,在对应的方法里边写自己更新的内容就可以了。
- (void)upgradeFrom1To2 {
//这里执行Sql语句 执行版本1到版本2的更新
FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath];
NSNumber *userId = [NSNumber numberWithLongLong:[UserManeger shareInstance].currentUser.uid];
if ([db open]) {
NSString* SysMessageSql = [NSString stringWithFormat:@"alter table SysMessage add userId int default %@",userId];
NSString* importSql = [NSString stringWithFormat:@"alter table User add userId int default %@",userId];
NSString* importChatSql = [NSString stringWithFormat:@"alter table UserChatMessage add userId int default %@",userId];
BOOL resSysMessage = [db executeUpdate:SysMessageSql];
BOOL res = [db executeUpdate:importSql];
BOOL resChat = [db executeUpdate:importChatSql];
if (res&&resSysMessage&&resChat) {
NSLog(@"更新User,UserChatMessage,SysMessage字段成功");
}else{
NSLog(@"更新User,UserChatMessage,SysMessage字段失败");
}
[db close];
}
}
一、数据库的自动更新迭代
说到自动更新迭代不得不提及runtime,扫盲一下--因为runtime可以获得属性的类型和属性。利用这一点特性进行数据库的自动更新迭代。(由于公司要做数据缓存,才写了这个自动更新换代的数据库)
首先和手动更新迭代一样创建单例数据库对象,全局调用
//创建CacheFMDB类的对象
static CacheFMDB* _instance = nil;
+ (instancetype)sharedCacheFMDB
{
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init] ;
}) ;
return _instance ;
}
我们首先要想怎么拼接数据库语句?
CREATE TABLE PositionTable (id integer PRIMARY KEY NOT NULL , isCollection int , isMyPosition int , isPublicPosition int , newAnswer int , statusId int , importantPos int , matchRate float , urgency int , positionType int , grabOrderPositionStatus int , productType int , lastUpdateTime double , uid double , annualSalary double , areaName text , cityId double , companyId double , createTime double , positionLogo text , companyName text , positionId double , maxShowAnnualSalary double , minShowAnnualSalary double , modifyTime double , publishTime double , suitableTalentCount double , positionTitle text , updateTime double , positionAveFeedBackDay text )
像上边这一个创建数据库语句的方法,
REATE TABLE %@ (id integer PRIMARY KEY NOT NULL
这些是固定的然后呢后边是不通的属性和对应的类型拼接而成。
那么c语言对应的oc语言的类型
static NSString *intType = @"i"; // int_32t,int
static NSString *longlongType = @"q"; // long,或者longlong
static NSString *floatType = @"f"; // float
static NSString *doubleType = @"d"; // double
static NSString *boolType = @"B"; // bool
static NSString *imageType = @"UIImage"; // UIImage 类型
static NSString *stringType = @"NSString"; // NSString 类型
static NSString *numberType = @"NSNumber"; // NSNumber 类型
用model便利每一个属性的变量名
Ivar *ivars = class_copyIvarList([model class], &count);
我们发现变量名字都多余了一个"_"。包括oc对应的储存的数据库语句。所以我进行进一步处理
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
//根据ivar获得其成员变量的名称
const char *name = ivar_getName(ivar);
//C的字符串转OC的字符串
NSString *key = [NSString stringWithUTF8String:name];
//放入数组
NSString *keyString = [key stringByReplacingOccurrencesOfString:@"_" withString:@""];
[_ivarsArray addObject:keyString];
// 获取变量类型,c字符串
const char *cType = ivar_getTypeEncoding(ivar);
//C的字符串转OC的字符串
NSString *Type = [NSString stringWithUTF8String:cType];
//基本类型数组库类型转化
NSLog(@"======%@",Type);
NSString *repleaceString = [self repleaceStringWithCSting:Type];
//放入数组
[_typeArray addObject:repleaceString];
}
/*****属性和数据库数据的类型相互转换*****/
- (NSString *)repleaceStringWithCSting:(NSString *)cSting{
if (![cSting isEqualToString:@""]) {
if ([cSting isEqualToString:@"i"]) {
return @"int";
}else if([cSting isEqualToString:@"q"]){
return @"double";
}else if([cSting isEqualToString:@"f"]){
return @"float";
}else if([cSting isEqualToString:@"d"]){
return @"double";
}else if([cSting isEqualToString:@"B"]){
return @"int";
}else if([cSting containsString:@"NSString"]){
return @"text";
}else if([cSting containsString:@"NSNumber"]){
return @"long";
}
NSAssert(1, @"handleSqliteTable类中 model的属性状态不对导致数据库状态不对,请核对后再拨");
return @"未知";
}else return nil;
}
准备工作就绪
我在处理数据库语句的时候写了个外部调用方法
//
// handleSqliteTable.h
// RuntimeDemo
//
// Created by peter on 16/3/18.
// Copyright © 2016年 hunteron All rights reserved.
//
#import <Foundation/Foundation.h>
@interface handleSqliteTable : NSObject
/**
* 获取 model 中的所有属性数组
* model 需要缓存的对象
*/
- (NSArray *)ivarsArrayWithModel:(NSObject *)model;
/**
* 返回 数据库表的语句
* tableName 表名字
* model 需要缓存的对象
*/
- (NSString *)sqliteStingWithTableName:(NSString *)tableName model:(NSObject *)model;
/*
*获取属性的类型,并转化为c的类型,并进行拼接
*/
- (NSArray *)attribleArray:(NSArray *)attribleArray model:(NSObject *)model;
@end
-----------------------数据库语句的拼接.m文件
//数据库拼接
- (NSString *)sqliteStingWithTableName:(NSString *)tableName model:(NSObject *)model{
[self test1:model];
return [self complatSqiteAttribiteA:_ivarsArray typeA:_typeArray tableName:tableName];
}
- (NSString *)complatSqiteAttribiteA:(NSArray *)attribiteA typeA:(NSArray *)typeA tableName:(NSString *)tableName{
NSString *string = [NSString stringWithFormat:@"CREATE TABLE %@ (id integer PRIMARY KEY NOT NULL",tableName];
NSString *beginString = @"";
for (int i = 0; i < attribiteA.count;i ++) {
NSString *atAndType = [self sqiteStringAttribite:(NSString *)attribiteA[i] type:(NSString *)typeA[i]];
beginString = [beginString stringByAppendingString:atAndType];
}
return [NSString stringWithFormat:@"%@ %@)",string,beginString];
}
/**
* 数据库语句拼接
*/
- (NSString *)sqiteStringAttribite:(NSString *)attribite type:(NSString *)type{
return [NSString stringWithFormat:@", %@ %@ ",attribite,type];
}
你把要转化的model通过
/**
* 返回 数据库表的语句
* tableName 表名字
* model 需要缓存的对象
*/
- (NSString *)sqliteStingWithTableName:(NSString *)tableName model:(NSObject *)model;
传过来,例如:
NSString * talentSql = [handel sqliteStingWithTableName:NSStringFromClass([talent class]) model:talent];
就会生成对应的数据库语句。
CREATE TABLE PositionTable (id integer PRIMARY KEY NOT NULL , isCollection int , isMyPosition int , isPublicPosition int , newAnswer int , statusId int , importantPos int , matchRate float , urgency int , positionType int , grabOrderPositionStatus int , productType int , lastUpdateTime double , uid double , annualSalary double , areaName text , cityId double , companyId double , createTime double , positionLogo text , companyName text , positionId double , maxShowAnnualSalary double , minShowAnnualSalary double , modifyTime double , publishTime double , suitableTalentCount double , positionTitle text , updateTime double , positionAveFeedBackDay text )
这才是第一步的数据库语句的拼接,是不是到这一步感觉已经好麻烦了??????没事不要着急
数据库的迁移无非是:
1.新增数据库(没有路径的情况)
2.新增表
3.增加字段
4.删除字段(sqlit3不支持字段的删除)
一、判断数据库路径是否存在
NSFileManager * fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:kCacheDBPath]) {
这里我就不说了
}else if([self needUpdateTabelArray:_modelArray]) {
NSLog(@"数据库已经存在路径,但需要新增数据库表:%@",kCacheDBPath);
//需要更新的表的名字
NSArray *tabelName = [self needUpdateTable:_modelArray];
//新建数据表
[self createNewTable:tabelName];
}else{
NSLog(@"数据库已经存在路径,不需要新增数据库表:%@",kCacheDBPath);
}
二、新增表
我会创建一个数组去记录表名字
_modelArray = [NSMutableArray array];
handleSqliteTable *handel = [[handleSqliteTable alloc]init];
//职位列表
PositionTable *position = [[PositionTable alloc]init];
[_modelArray addObject:position];
NSString * positionSql = [handel sqliteStingWithTableName:NSStringFromClass([position class]) model:position];
//人才列表
TalentTable *talent = [[TalentTable alloc]init];
[_modelArray addObject:talent];
NSString * talentSql = [handel sqliteStingWithTableName:NSStringFromClass([talent class]) model:talent];
判断有没有这个表数据库中
//多个表是不是要更新
- (BOOL)needUpdateTabelArray:(NSArray *)array{
for (NSObject *obj in array) {
NSString *string = NSStringFromClass([obj class]);
NSLog(@"=====%d",[self needUpdateTabel:string]);
if(![self needUpdateTabel:string]) {
return YES;
}
}
return NO;
}
//检查数据库的表是否存在
- (BOOL)needUpdateTabel:(NSString *)tableName{
FMDatabase * db = [FMDatabase databaseWithPath:kCacheDBPath];
BOOL need = NO;
if ([db open]) {
//得到所有的表表名
FMResultSet *rs = [db executeQuery:@"select count(*) as 'count' from sqlite_master where type ='table' and name = ?", tableName];
while ([rs next])
{
// just print out what we've got in a number of formats.
NSInteger count = [rs intForColumn:@"count"];
NSLog(@"isTableOK %ld", (long)count);
if (0 == count)
{
need = NO;
}
else
{
need = YES;
}
}
[rs close];
[db close];
}
return need;
}
表不存在就
//新增表
- (void)createNewTable:(NSArray *)array{
for (NSObject *obj in _modelArray) {
NSString *string = NSStringFromClass([obj class]);
if ([array containsObject:string]) {
handleSqliteTable *handel = [[handleSqliteTable alloc]init];
NSString * tableSqilet = [handel sqliteStingWithTableName:NSStringFromClass([obj class]) model:obj];
[self createATable:tableSqilet];
}
}
}
检查表里边的字段需不需要更新,去现在有的model的属性和数据库中的比较找出需要更新的字段
//自动更新机制 表的对应的model变化,需要对表做相应的增加和删除操做
for (NSObject *obj in _modelArray) {
//runtime 获取现有model的所有属性string
NSArray *array = [handel ivarsArrayWithModel:obj];
//对比数据库和现有的属性sting
[self checkAndUpdateTable:obj newAttribe:array];
}
//判断新老表中有没有新增字段
- (void)checkAndUpdateTable:(NSObject*)objName newAttribe:(NSArray *)newAttribe{
//数据库中现有的字段
NSMutableArray *sqliteArray = [NSMutableArray array];
NSString *tableName = NSStringFromClass([objName class]);
FMDatabase * db = [FMDatabase databaseWithPath:kCacheDBPath];
if ([db open]) {
NSString * sql = [NSString stringWithFormat:@"select * from %@",tableName] ;
FMResultSet * rs = [db executeQuery:sql];
NSDictionary * dict = [rs columnNameToIndexMap];
[sqliteArray addObjectsFromArray:[dict allKeys]];
[db close];
}
//需要更新的字段
NSMutableArray *needUpdateName =[NSMutableArray array];
for (NSString *string in newAttribe) {
NSString * lowercaseString = [string lowercaseString];
if (![sqliteArray containsObject:lowercaseString]) {
[needUpdateName addObject:string];
}
}
handleSqliteTable *handel = [[handleSqliteTable alloc]init];
if (needUpdateName.count > 0) {
NSArray *array = [handel attribleArray:needUpdateName model:objName];
//更新
[self updateTabelupdateString:array tableName:tableName];
}
}
//增加新的表字段
- (void)updateTabelupdateString:(NSArray *)updateArray tableName:(NSString *)tableName{
FMDatabase * db = [FMDatabase databaseWithPath:kCacheDBPath];
if ([db open]) {
for (NSString *updateString in updateArray) {
NSString* SysMessageSql = [NSString stringWithFormat:@"alter table %@ add %@",tableName,updateString];
BOOL resSysMessage = [db executeUpdate:SysMessageSql];
if (resSysMessage) {
NSLog(@"新增%@表字段%@成功",tableName,updateString);
}else{
NSLog(@"新增%@表字段%@失败",tableName,updateString);
}
}
[db close];
}
}
这就完成了9成的更新功能。
还有一成更新表的增删改查语句。同样的道理更新数据库语句.......