1# 数据库备份与恢复 (ArkTS) 2<!--Kit: ArkData--> 3<!--Subsystem: DistributedDataManager--> 4<!--Owner: @baijidong--> 5<!--Designer: @widecode; @htt1997; @dboy190--> 6<!--Tester: @yippo; @logic42--> 7<!--Adviser: @ge-yafang--> 8 9## 场景介绍 10 11当应用在处理一项重要的操作,显然是不能被打断的。例如:写入多个表关联的事务。此时,每个表的写入都是单独的,但是表与表之间的事务关联性不能被分割。 12 13如果操作的过程中出现问题,开发者可以使用恢复功能,将数据库恢复到之前的状态,重新对数据库进行操作。 14 15在数据库被篡改、删除、或者设备断电场景下,数据库可能会因为数据丢失、数据损坏、脏数据等而不可用,可以通过数据库的备份恢复能力将数据库恢复至可用状态。 16 17 18键值型数据库和关系型数据库均支持对数据库的备份和恢复。另外,键值型数据库还支持删除数据库备份,以释放本地存储空间。 19 20 21## 键值型数据库备份、恢复与删除 22 23键值型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复,通过deletebackup接口删除数据库备份。具体接口及功能,可见[分布式键值数据库](../reference/apis-arkdata/js-apis-distributedKVStore.md)。 24 251. 创建数据库。 26 27 (1) 创建kvManager。 28 29 (2) 配置数据库参数。 30 31 (3) 创建kvStore。 32 33 34 ```ts 35 import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; 36 import { hilog } from '@kit.PerformanceAnalysisKit'; 37 import { distributedKVStore } from '@kit.ArkData'; 38 import { BusinessError } from '@kit.BasicServicesKit'; 39 40 export default class EntryAbility extends UIAbility { 41 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 42 this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); 43 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 44 let kvManager: distributedKVStore.KVManager; 45 let kvStore: distributedKVStore.SingleKVStore | undefined = undefined; 46 let context = this.context; 47 const kvManagerConfig: distributedKVStore.KVManagerConfig = { 48 context: context, 49 bundleName: 'com.example.datamanagertest' 50 } 51 try { 52 kvManager = distributedKVStore.createKVManager(kvManagerConfig); 53 console.info('Succeeded in creating KVManager.'); 54 try { 55 const options: distributedKVStore.Options = { 56 createIfMissing: true, 57 encrypt: true, 58 backup: false, 59 autoSync: false, 60 kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION, 61 securityLevel: distributedKVStore.SecurityLevel.S3 62 }; 63 kvManager.getKVStore<distributedKVStore.SingleKVStore>('storeId', options, (err, store: distributedKVStore.SingleKVStore) => { 64 if (err) { 65 console.error(`Failed to get KVStore. Code:${err.code},message:${err.message}`); 66 return; 67 } 68 console.info('Succeeded in getting KVStore.'); 69 kvStore = store; 70 if (kvStore !== undefined) { 71 // 进行后续操作 72 // ... 73 } 74 }); 75 } catch (e) { 76 let error = e as BusinessError; 77 console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`); 78 } 79 } catch (e) { 80 let error = e as BusinessError; 81 console.error(`Failed to create KVManager. Code:${error.code},message:${error.message}`); 82 } 83 } 84 } 85 ``` 86 872. 使用put()方法插入数据。 88 89 ```ts 90 const KEY_TEST_STRING_ELEMENT = 'key_test_string'; 91 const VALUE_TEST_STRING_ELEMENT = 'value_test_string'; 92 try { 93 kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => { 94 if (err !== undefined) { 95 console.error(`Fail to put data. Code:${err.code},message:${err.message}`); 96 return; 97 } 98 console.info('Succeeded in putting data.'); 99 }); 100 } catch (e) { 101 let error = e as BusinessError; 102 console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`); 103 } 104 ``` 105 1063. 使用backup()方法备份数据。 107 108 ```ts 109 let backupFile = 'BK001'; 110 try { 111 kvStore.backup(backupFile, (err) => { 112 if (err) { 113 console.error(`Fail to backup data.code:${err.code},message:${err.message}`); 114 } else { 115 console.info('Succeeded in backuping data.'); 116 } 117 }); 118 } catch (e) { 119 let error = e as BusinessError; 120 console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`); 121 } 122 ``` 123 1244. 使用delete()方法删除数据(模拟意外删除、篡改场景)。 125 126 ```ts 127 try { 128 kvStore.delete(KEY_TEST_STRING_ELEMENT, (err) => { 129 if (err !== undefined) { 130 console.error(`Fail to delete data. Code:${err.code},message:${err.message}`); 131 return; 132 } 133 console.info('Succeeded in deleting data.'); 134 }); 135 } catch (e) { 136 let error = e as BusinessError; 137 console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`); 138 } 139 ``` 140 1415. 使用restore()方法恢复数据。 142 143 ```ts 144 try { 145 kvStore.restore(backupFile, (err) => { 146 if (err) { 147 console.error(`Fail to restore data. Code:${err.code},message:${err.message}`); 148 } else { 149 console.info('Succeeded in restoring data.'); 150 } 151 }); 152 } catch (e) { 153 let error = e as BusinessError; 154 console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`); 155 } 156 ``` 157 1586. 当本地设备存储空间有限或需要重新备份时,还可使用deleteBackup()方法删除备份,释放存储空间。 159 160 ```ts 161 let files = [backupFile]; 162 try { 163 kvStore.deleteBackup(files).then((data) => { 164 console.info(`Succeed in deleting Backup. Data:filename is ${data[0]},result is ${data[1]}.`); 165 }).catch((err: BusinessError) => { 166 console.error(`Fail to delete Backup. Code:${err.code},message:${err.message}`); 167 }) 168 } catch (e) { 169 let error = e as BusinessError; 170 console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`); 171 } 172 ``` 173 174## 关系型数据库备份 175 176数据库操作或者存储过程中,有可能会因为各种原因发生非预期的数据库异常的情况,可以根据需要使用关系型数据库的备份能力,以便在数据库异常时,可靠高效地恢复数据保证业务数据正常使用。 177 178关系型数据库支持手动备份和自动备份(仅系统应用可用)两种方式。 179 180### 手动备份 181 182手动备份:通过调用[backup](../reference/apis-arkdata/arkts-apis-data-relationalStore-RdbStore.md#backup)接口实现数据库手动备份。示例如下: 183 184```ts 185import { UIAbility } from '@kit.AbilityKit'; 186import { relationalStore } from '@kit.ArkData'; 187import { BusinessError } from '@kit.BasicServicesKit'; 188 189export default class EntryAbility extends UIAbility { 190 async onCreate(): Promise<void> { 191 let store: relationalStore.RdbStore | undefined = undefined; 192 let context = this.context; 193 194 const STORE_CONFIG: relationalStore.StoreConfig = { 195 name: 'RdbTest.db', 196 securityLevel: relationalStore.SecurityLevel.S3, 197 allowRebuild: true 198 }; 199 try { 200 store = await relationalStore.getRdbStore(context, STORE_CONFIG); 201 await store.executeSql('CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)'); 202 console.info('Succeeded in getting RdbStore.'); 203 } catch (e) { 204 const err = e as BusinessError; 205 console.error(`Failed to get RdbStore. Code:${err.code},message:${err.message}`); 206 } 207 208 if (!store) { 209 return; 210 } 211 212 try { 213 /** 214 * "Backup.db"为备份数据库文件名,默认在RdbStore同路径下备份。 215 * 也可指定绝对路径:"/data/storage/el2/database/Backup.db",文件路径需要存在,不会自动创建目录。 216 */ 217 await store.backup("Backup.db"); 218 console.info(`Succeeded in backing up RdbStore.`); 219 } catch (e) { 220 const err = e as BusinessError; 221 console.error(`Failed to backup RdbStore. Code:${err.code}, message:${err.message}`); 222 } 223 } 224} 225``` 226 227<!--Del--> 228 229### 自动备份(仅系统应用可用) 230 231自动备份:可以通过在[StoreConfig](../reference/apis-arkdata/js-apis-data-relationalStore-sys.md#storeconfig)中配置haMode参数为MAIN_REPLICA实现数据库双写备份,仅支持系统应用。示例如下: 232 233```ts 234import { UIAbility } from '@kit.AbilityKit'; 235import { relationalStore } from '@kit.ArkData'; 236import { BusinessError } from '@kit.BasicServicesKit'; 237 238export default class EntryAbility extends UIAbility { 239 async onCreate(): Promise<void> { 240 let store: relationalStore.RdbStore | undefined = undefined; 241 let context = this.context; 242 try { 243 // 配置StoreConfig的haMode参数为MAIN_REPLICA。 244 const AUTO_BACKUP_CONFIG: relationalStore.StoreConfig = { 245 name: "BackupRestoreTest.db", 246 securityLevel: relationalStore.SecurityLevel.S3, 247 haMode: relationalStore.HAMode.MAIN_REPLICA, // 配置为双写备份 248 allowRebuild: true 249 } 250 251 // 使用getRdbStore()方法创建关系型数据库。 252 store = await relationalStore.getRdbStore(context, AUTO_BACKUP_CONFIG); 253 console.info('Succeeded in getting RdbStore.'); 254 } catch (e) { 255 const err = e as BusinessError; 256 console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`); 257 } 258 } 259} 260``` 261 262<!--DelEnd--> 263 264## 关系型数据库异常重建 265 266在创建或使用关系型数据库的过程中,抛出14800011异常错误码说明数据库出现异常,可以删除数据库后恢复数据。 267 268需要通过在[StoreConfig](../reference/apis-arkdata/arkts-apis-data-relationalStore-i.md#storeconfig)中配置allowRebuild参数为true以设置数据库在出现异常时自动删库。数据库重建成功后为空库,需要开发者重新建表并且使用提前备份好的数据进行数据恢复,备份操作可见[关系型数据库备份](#关系型数据库备份),数据恢复可见[关系型数据库恢复](#关系型数据库数据恢复)。 269 270若数据库异常前已配置StoreConfig中的allowRebuild为true,则数据库出现异常时将自动删库。 271 272若数据库异常前未配置StoreConfig中的allowRebuild或allowRebuild配置为false,则需将其配置为true再次进行开库。具体示例如下: 273 274```ts 275import { UIAbility } from '@kit.AbilityKit'; 276import { relationalStore } from '@kit.ArkData'; 277import { BusinessError } from '@kit.BasicServicesKit'; 278 279export default class EntryAbility extends UIAbility { 280 async onCreate(): Promise<void> { 281 let store: relationalStore.RdbStore | undefined = undefined; 282 let context = this.context; 283 try { 284 const STORE_CONFIG: relationalStore.StoreConfig = { 285 name: 'RdbTest.db', 286 securityLevel: relationalStore.SecurityLevel.S3, 287 allowRebuild: true 288 }; 289 store = await relationalStore.getRdbStore(context, STORE_CONFIG); 290 await store.executeSql('CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)'); 291 console.info('Succeeded in getting RdbStore.'); 292 } catch (e) { 293 const err = e as BusinessError; 294 console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`); 295 } 296 } 297} 298``` 299 300## 关系型数据库数据恢复 301 302针对数据库出现异常的情况,在数据库重建成功后,需要用提前备份好的数据进行数据恢复。 303 304恢复方式分以下两种,手动备份恢复和自动备份恢复(仅系统应用可用)。 305 306### 恢复手动备份数据 307 308关系型数据库通过调用backup接口可以实现[手动备份数据库](#手动备份),通过restore接口可以实现手动恢复数据库。 309 310具体恢复过程和关键示例代码片段如下,完整示例代码请结合关系型数据库的备份、重建等上下文进行实现。 311 3121. 抛出数据库异常错误码。 313 314 ```ts 315 let predicates = new relationalStore.RdbPredicates("EMPLOYEE"); 316 if (store != undefined) { 317 (store as relationalStore.RdbStore).query(predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"]).then((result: relationalStore.ResultSet) => { 318 let resultSet = result; 319 try { 320 /* ... 321 业务的增删改逻辑 322 ... 323 */ 324 // 抛出异常 325 if (resultSet?.rowCount == -1) { 326 resultSet ?.isColumnNull(0); 327 } 328 // todo resultSet.goToFirstRow(), resultSet.count等其它接口也会抛异常 329 while (resultSet.goToNextRow()) { 330 console.info(JSON.stringify(resultSet.getRow())) 331 } 332 resultSet.close(); 333 } catch (err) { 334 if (err.code === 14800011) { 335 // 执行下文的步骤,即关闭结果集之后进行数据的恢复 336 } 337 console.error(JSON.stringify(err)); 338 } 339 }) 340 } 341 ``` 342 3432. 关闭所有打开着的结果集。 344 345 ```ts 346 // 获取所有打开着的结果集 347 let resultSets: Array<relationalStore.ResultSet> = []; 348 // 使用resultSet.close()方法关闭所有打开着的结果集 349 for (let resultSet of resultSets) { 350 try { 351 resultSet.close(); 352 } catch (e) { 353 if (e.code !== 14800014) { 354 console.error(`Code:${e.code}, message:${e.message}`); 355 } 356 } 357 } 358 ``` 359 3603. 调用restore接口恢复数据。 361 362 ```ts 363 import { UIAbility } from '@kit.AbilityKit'; 364 import { relationalStore } from '@kit.ArkData'; 365 import { BusinessError } from '@kit.BasicServicesKit'; 366 import { fileIo } from '@kit.CoreFileKit'; 367 368 export default class EntryAbility extends UIAbility { 369 async onCreate(): Promise<void> { 370 let store: relationalStore.RdbStore | undefined = undefined; 371 let context = this.context; 372 let STORE_CONFIG: relationalStore.StoreConfig = { 373 name: "RdbTest.db", 374 securityLevel: relationalStore.SecurityLevel.S3, 375 allowRebuild: true 376 } 377 try { 378 /** 379 * "Backup.db"为备份数据库文件名,默认在当前 store 所在路径下查找备份文件 Backup.db。 380 * 如在备份时指定了绝对路径:"/data/storage/el2/database/Backup.db", 需要传入绝对路径。 381 */ 382 let backupFilePath = context.databaseDir + '/rdb/Backup.db'; 383 const backupExist = await fileIo.access(backupFilePath); 384 if (!backupExist) { 385 console.info("Backup is not exist."); 386 // todo 开库建表 387 // todo 自行生成数据 388 return; 389 } 390 } catch (e) { 391 console.error(`Code:${e.code}, message:${e.message}`); 392 } 393 394 try { 395 store = await relationalStore.getRdbStore(context, STORE_CONFIG); 396 // 调用restore接口恢复数据 397 await store.restore("Backup.db"); 398 console.log("Restore from back success.") 399 } catch (e) { 400 const err = e as BusinessError; 401 console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`); 402 } 403 } 404 } 405 ``` 406 407<!--Del--> 408 409### 恢复自动备份数据(仅系统应用可用) 410 411关系型数据库,可以通过[restore](../reference/apis-arkdata/js-apis-data-relationalStore-sys.md#restore12)接口恢复[自动备份的数据](#自动备份仅系统应用可用),仅支持系统应用。 412 413关键示例代码片段如下,完整示例代码请结合关系型数据库的备份、重建等上下文进行实现。 414 415 ```ts 416 if (store !== undefined) { 417 try { 418 // 增删改查 419 } catch (err) { 420 if (err.code == 14800011) { 421 // 获取所有打开着的结果集 422 let resultSets: Array<relationalStore.ResultSet> = []; 423 // 使用resultSet.close()方法关闭所有打开着的结果集 424 for (let resultSet of resultSets) { 425 try { 426 resultSet.close(); 427 } catch (e) { 428 if (e.code !== 14800014) { 429 console.error(`Code:${e.code}, message:${e.message}`); 430 } 431 } 432 } 433 434 (store as relationalStore.RdbStore).restore("Backup.db", (err: BusinessError) => { 435 if (err) { 436 console.error(`Failed to restore RdbStore. Code:${err.code}, message:${err.message}`); 437 return; 438 } 439 console.info(`Succeeded in restoring RdbStore.`); 440 }) 441 } 442 console.error(`Code:${err.code}, message:${err.message}`); 443 } 444 } 445 ``` 446 447<!--DelEnd--> 448 449<!--RP1--><!--RP1End-->