基于FMDB的本地数据库版本迭代(iOS)

2019-06-04  本文已影响0人  Peak_Liang

新到公司,项目里有一个本地数据库,最新接到需求需要维护,经过一番了解之后,甚感头大,本地数据库经过26次本地迭代升级,版本迭代代码就有460行,而且每次迭代本地还要维护.sqlite文件用与新用户的本地数据库初始化。懒惰的我就对这种工序多,代码繁琐的迭代方式产生了怀疑。于是我就去搜索了一下,发现,emmmm,好像博客论坛里的也是这么一回事。

然后我就开始思考数据库迭代所需要做的工作。看看能不能简化这个流程,或者寻找到新的迭代方式。

维护数据库无外乎两点:表结构和旧数据。现行的做法是本地保存一个数据库版本号,每次迭代更新一个版本号,在已有的旧表基础上对表结构进行修改,即新建/删除表,新建/删除列。但是在新安装的App中没有本地保存的版本号,这时候就有两种做法,一是初始版本号为0 ,从最初版本开始递归执行版本迭代方法,执行完所有的版本迭代方法后,版本号升为最新;另一种方法是本地建一个.sqlite文件,保持为最新的表结构,第一次启动时直接将这个文件内的表拷贝到沙盒中并将版本号升为最新。

- (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;
        default:
          break;
  }
  oldVersion ++;
  // 递归判断是否需要升级:保证老版本从最低升级到当前
  [self upgrade:oldVersion];
}

这种做法最大的问题,是在原表上修改表结构。对于已安装App的更新,只需要执行更新操作;而对于第一次启动,则需要执行全部操作;因此需要记录所有的版本升级操作来兼容任意一个旧版本的升级。

如果不考虑数据,我们更新表结构的方法有两种:

上面的方案就是第一种更新表结构的方法延伸出来的,那么我尝试从第二种方法考虑。如果我要用新表制作新的表结构,那么对于已有的旧表中的数据,就需要做数据迁移。将旧表中的数据迁移到新表中来,已经废弃的列的数据丢弃,新加的列保持为空或者赋默认值,这样就完成了对这个表的迭代操作。如何对表做数据迁移呢?

result = [db executeUpdate:@"INSERT INTO tableNew (column) SELECT column FROM tableOld"];

这个命令可以将右表中的指定列迁移到左表的指定列中。其中自增主键 “id” 不需要迁移。

基于这个思路,来看看我们都需要做哪些工作。

和现行的方法不同的是,我们的迭代方法是每次都要执行的,方法同样具有创建新表(为被使用过的表名)的职责,这同样也是版本迭代可能存在行为之一。如果我们对所有的表采用同一个版本号管理,那么当我们执行任意一个表的迭代时,所有的表都会执行一次迭代操作,虽然表结构没有发生变化,但是会进行数据迁移。因此我给每一个表都独立了一个版本号,并新建了一张表保存这些版本号,当版本号取不到时,就认为是新建表。当迭代完成后,将新的版本号保存到表中。

我们对表进行迭代,在没有需求要求的情况下,迭代前后的表名应该一致。即新建的表与原表的表名一致,这就要求我们对旧表先改名。所以如果表名修改成功,我们就认为此表存在,反之,如果表名修改失败,用原表名新建表成功,说明这个表本就不存在。

新表中的列是我们当前迭代的内容,旧表中的列由于存在版本差异需要我们去旧表中取。

一切准备就续,就可以着手尝试了。

首先要创建一个版本记录表,表中维护当前数据库的版本号,代码中运行一个将要升级的版本号字典用于比对,在程序运行时,检查是否有表需要迭代升级。

__block BOOL resultVersion;
__block NSMutableDictionary *needUpdate = [NSMutableDictionary dictionary];
[self.databaseQueue inDatabase:^(FMDatabase * _Nonnull db) {
    // 创建版本l记录表
    resultVersion = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS tableVersion (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, version DOUBLE)"];
    if (resultVersion) {
        NSLog(@"创建tableVersion成功");
    } else {
        NSLog(@"创建tableVersion失败");
        return;
    }
    // 代码运行的表版本号.需要维护,版本号默认请从1.0开始
    NSMutableDictionary *versionDict = [NSMutableDictionary         dictionaryWithDictionary:@{@"tableNew" : @1.4}];
    FMResultSet *versions = [db executeQuery:@"SELECT * FROM tableVersion"];
    while ([versions next]) {
        NSString *table = [versions stringForColumn:@"name"];
        double version = [versions doubleForColumn:@"version"];
        double dictVerion = [versionDict[table] doubleValue];
        if (dictVerion > version ) {
            // 已经保存过且版本号需要更新
            [needUpdate addEntriesFromDictionary:@{table : @(dictVerion)}];
        }
        [versionDict removeObjectForKey:table];
    }
    [versions close];
    // 本地没有记录的
    for (NSString *key in versionDict.allKeys) {
        double dictVerion = [versionDict[key] doubleValue];
        [needUpdate addEntriesFromDictionary:@{key : @(dictVerion)}];
        // 因表还未创建,此处版本号设置为0,创建成功后更新为当前版本号
        BOOL resultSaveVersion = [db executeUpdate:@"INSERT INTO tableVersion (name,version) VALUES (?,?)", key, @0];
        if (resultSaveVersion) {
            NSLog(@"保存版本号成功");
        }
    }
}];

对需要升级的表执行升级操作

for (NSString *key in needUpdate.allKeys) {
    double dictVerion = [needUpdate[key] doubleValue];
    [self updateTablesWithTableName:key newVersion:dictVerion];
}

方法中包含创建表的sql语句,表名,列名,和待升级版本号

- (void)updateTablesWithTableName:(NSString *)table newVersion:(double)version{
    // 在此处维护表结构
    if ([@"tableNew" isEqualToString:table]) {
        [self updateTableWithSql:@"CREATE TABLE IF NOT EXISTS tableNew (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, address TEXT)" tableName:@"tableNew" columns:@[@"name", @"address"] newVersion:version];
    }
}

然后开始迭代

总结:

  1. 此方案将原方案基于时间的纵向增长改变为基于表数量的横向增长,只需要维护表创建和版本号就可以完成迭代。

2.此方案对比原方案,动作更大,需要对表中所有数据进行迁移,一旦出现未知错误可能造成的影响也更大,虽然在执行失败时做了回退操作,但不能完美解决安全问题,需要在安全性方面做更多的改进。

3.附上原方案的大神版介绍,希望各位大佬多加指正。https://www.cnblogs.com/PeterWolf/p/6211905.html

上一篇 下一篇

猜你喜欢

热点阅读