ios開發數據庫版本遷移手動更新迭代和自動更新迭代藝術(-)


demo 地址 https://github.com/PureLovePeter/DataCache 好用的話 star star star

數據庫版本遷移顧名思義就是在原有的數據庫中更新數據庫,數據庫中的數據保持不變對表的增、刪、該、查。

數據持久化存儲:

  • 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];

    }

}

 

 

一、數據庫的自動更新迭代 

數據庫自動更新顧名思義就是:

1.數據庫中有的不需要的表刪除、需要的沒有的表的增加

2.對數據庫已有的不需要的字段刪除、需要的沒有的字段增加;

 代碼詳解:

說到自動更新迭代不得不提及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成的更新功能。

還有一成更新表的增刪改查語句。同樣的道理更新數據庫語句.......

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM