-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Swift 数据迁移
APP发展早期的数据库库表设计常有考虑不足的情况,一个容易犯的错误是把很多种不同的数据都存储到一个数据库中。这样会有两个问题:
- SQLite同个数据库不支持并行写入,这样不同表就无法同时更新。
- 同个数据库承载的逻辑越多,读写也就越多,数据库也就越容易损坏,而且损坏的损失也越大。
为了解决这些问题,就需要把数据表迁移到不同的数据库。数据迁移过程是比较慢的,数据迁移处于中间状态时,总不能阻塞用户使用这个数据相关的功能。如果要使用在迁移过程中的数据,就需要同时读取新表和旧表的数据,写入数据也要考虑迁移状态的问题。这样就会导致数据读写的代码需要维护两套,而且因为很难找到一个时间点界定所有用户的数据都迁移完成了,所以这两套数据读写代码要一直维护着,而且新数据库逻辑也要考虑迁移状态,也要写成两份,这样就很麻烦了。
为了解决数据迁移的中间状态问题,WCDB 就提出了一个新概念。由 WCDB 来解决兼容问题,让开发者可以 以迁移已经完成为假定前提 进行开发。同时因为是框架层代码,天然就是 code once, run everywhere,所以开发也不需要花费时间在迁移的灰度上,也无需考虑数据迁移的中间状态。下面是数据迁移功能的配置示例:
// 创建源数据库和源表
let sourceDatabase = Database(at: sourcePath)
try sourceDatabase.create(table: "sourceTable", of: Sample.self)
// 写入待迁移的数据到源表
let oldObject1 = Sample()
oldObject1.identifier = 1;
oldObject1.description = "oldContent1"
let oldObject2 = Sample()
oldObject2.identifier = 2;
oldObject2.description = "oldContent2"
try sourceDatabase.insert(oldObject1, oldObject2, intoTable: "sourceTable")
// 创建迁移数据的目标数据库
let targetDatabase = Database(at: targetPath)
// 数据迁移配置
// targetDatabase中的所有表格都调用这个回调,需要迁移的表格需要配置 sourceTable 和 sourceDatabase
// 这个配置需要在所有targetDatabase数据操作前配置
targetDatabase.addMigration(sourcePath: sourcePath)({ info in
if info.table == "targetTable" {
info.sourceTable = "sourceTable"
}
})
// 创建数据迁移的目标表格
// 目标表格使用的ORM类要和源表一致
try targetDatabase.create(table: "targetTable", of: Sample.self)
WCDB 还支持配置迁移源表中的部分数据,实现方法是在
addMigration
的回调参数的filterCondition
属性上配置一个筛选部分数据的表达式,借用这个功能可以将同个表的数据拆分到不同的表。
配置好之后,就可以认为数据迁移已经瞬间完成,可以直接使用目标表格来操作源表的数据,示例代码如下:
// 使用目标表格更新数据
try targetDatabase.update(table: "targetTable",
on: Sample.Properties.description,
with: "newContent2",
where: Sample.Properties.identifier == 2)
let objects: [Sample] = try targetDatabase.getObjects(fromTable: "targetTable")
XCTAssertEqual(objects.count, 2)
XCTAssertEqual(objects[1].description, "newContent2")
// 使用目标表格删除数据
try targetDatabase.delete(fromTable: "targetTable", where: Sample.Properties.identifier == 2)
let count = try targetDatabase.getValue(on: Sample.Properties.any.count(), fromTable: "targetTable")
XCTAssertEqual(count?.int32Value ?? 0, 1)
// 使用目标表格写入新数据
let newObject = Sample()
newObject.identifier = 3
newObject.description = "newContent3"
try targetDatabase.insert(newObject, intoTable: "targetTable")
let descriptions: OneColumnValue = try targetDatabase.getColumn(on: Sample.Properties.description,
fromTable: "targetTable",
orderBy: [Sample.Properties.identifier.order(.ascending)]))
XCTAssertEqual(descriptions.count, 2)
XCTAssertEqual(descriptions[0].stringValue, "oldContent1")
XCTAssertEqual(descriptions[1].stringValue, "newContent3")
虽然配置之后可以把数据看做已经完全迁移到目标表格了,但是实际数据还在源表格,数据迁移不可能一瞬间完成,需要额外的逻辑将数据迁移过来。
可以使用Database.setAutoMigration(enable:)
接口配置自动迁移,WCDB 会每隔两秒使用大概0.01秒迁移一次数据,直到数据迁移完成;也可以使用Database.stepMigration()
接口手动迁移,自己控制数据迁移的节奏。可以使用Database.setNotification(whenMigrated:)
接口注册迁移进度的监听,每次迁移完一个表格都会回调。下面是迁移过程的使用示例:
XCTAssertEqual(targetDatabase.isMigrated(), false)
var migratedTable: String? = nil
targetDatabase.setNotification { database, info in
if let sourceTable = info?.sourceTable {
migratedTable = sourceTable
}
}
repeat {
XCTAssertNoThrow(try targetDatabase.stepMigration())
} while !targetDatabase.isMigrated()
XCTAssertEqual(targetDatabase.isMigrated(), true)
XCTAssertEqual(migratedTable ?? "", "sourceTable")
跨数据库迁移时,需要将源数据库 attach 到目标数据库。源数据库如果是加密数据库,就需要在 attach 时将源数据库解密才能 attach 成功。开发者可以在调用Database.addMigration(sourcePath:sourceCipher:_:)
方法时将源数据库的密码一并传入。
一个需要注意的点是, SQLCipher 在 attach 加密数据库时,只支持传加密 Key 这一个参数,其他配置都是用当前进程的默认配置。如果源数据库不是按照默认配置加密的,就无法在 attach 时解密成功了。所以源数据库必须使用当前进程的默认加密配置。开发者可以使用 class Database.setDefaultCipherConfiguration(_:)
接口更改当前进程的默认加密配置,不过要处理好全局配置和其他的加密数据库的加密配置的冲突。
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程