数据库备份与恢复 (ArkTS)
场景介绍
如果操作或存储的过程中出现问题,开发者可以使用恢复功能,将数据库恢复到之前的状态,重新对数据库进行操作。
在数据库被篡改、删除、或者设备断电场景下,数据库可能会因为数据丢失、数据损坏、脏数据等而不可用,可以通过数据库的备份恢复能力将数据库恢复至可用状态。
键值型数据库和关系型数据库均支持对数据库的备份和恢复。另外,键值型数据库还支持删除数据库备份,以释放本地存储空间。
键值型数据库备份、恢复与删除
键值型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复,通过deletebackup接口删除数据库备份。具体接口及功能,可见分布式键值数据库。
-
创建数据库。
(1) 创建kvManager。
(2) 配置数据库参数。
(3) 创建kvStore。
// 导入模块// 在pages目录下新建KvStoreInterface.etsimport { distributedKVStore } from '@kit.ArkData';import { BusinessError } from '@kit.BasicServicesKit';import EntryAbility from '../entryability/EntryAbility';// Logger为hilog封装后实现的打印功能import Logger from '../common/Logger';let kvManager: distributedKVStore.KVManager | undefined = undefined;let kvStore: distributedKVStore.SingleKVStore | undefined = undefined;let appId: string = 'com.example.kvstoresamples';let storeId: string = 'storeId';const context = EntryAbility.getContext();// 下面所有接口的代码都实现在KvInterface中export class KvInterface {}public CreateKvManager = (() => {Logger.info('CreateKvManager start');if (typeof (kvManager) === 'undefined') {const kvManagerConfig: distributedKVStore.KVManagerConfig = {bundleName: appId,context: context};try {// 创建KVManager实例kvManager = distributedKVStore.createKVManager(kvManagerConfig);Logger.info('Succeeded in creating KVManager.');} catch (err) {Logger.error(`Failed to create KVManager. Code:${err.code},message:${err.message}`);}} else {Logger.info ('KVManager has created');}})public GetKvStore = (() => {Logger.info('GetKvStore start');if (kvManager === undefined) {Logger.info('KvManager not initialized');return;}try {let child1 = new distributedKVStore.FieldNode('id');child1.type = distributedKVStore.ValueType.INTEGER;child1.nullable = false;child1.default = '1';let child2 = new distributedKVStore.FieldNode('name');child2.type = distributedKVStore.ValueType.STRING;child2.nullable = false;child2.default = 'zhangsan';let schema = new distributedKVStore.Schema();schema.root.appendChild(child1);schema.root.appendChild(child2);schema.indexes = ['$.id', '$.name'];// 0表示COMPATIBLE模式,1表示STRICT模式。schema.mode = 1;// 支持在检查Value时,跳过skip指定的字节数,且取值范围为[0,4M-2]。schema.skip = 0;const options: distributedKVStore.Options = {createIfMissing: true,// 设置数据库加密encrypt: true,backup: false,autoSync: false,// kvStoreType不填时,默认创建多设备协同数据库kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,// 多设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,schema: schema,// schema未定义可以不填,定义方法请参考上方schema示例。securityLevel: distributedKVStore.SecurityLevel.S3};kvManager.getKVStore<distributedKVStore.SingleKVStore>(storeId, options,(err, store: distributedKVStore.SingleKVStore) => {if (err) {Logger.error(`Failed to get KVStore: Code:${err.code},message:${err.message}`);return;}Logger.info('Succeeded in getting KVStore.');kvStore = store;// 请确保获取到键值数据库实例后,再进行相关数据操作});} catch (e) {let error = e as BusinessError;Logger.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);}}) -
使用put()方法插入数据。
public Put = (() => {Logger.info('Put start');if (kvStore === undefined) {Logger.info('Put: kvStore not initialized');return;}const KEY_TEST_STRING_ELEMENT = 'key_test_string';// 如果未定义Schema则Value可以传其他符合要求的值。const VALUE_TEST_STRING_ELEMENT = '{"id":0, "name":"lisi"}';try {kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {if (err !== undefined) {Logger.error(`Failed to put data. Code:${err.code},message:${err.message}`);return;}Logger.info('Succeeded in putting data.');});} catch (e) {let error = e as BusinessError;Logger.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);}}) -
使用backup()方法备份数据。
public Backup = (() => {Logger.info('Backup start');if (kvStore === undefined) {Logger.info('Backup: kvStore not initialized');return;}let backupFile = 'BK001';try {kvStore.backup(backupFile, (err) => {if (err) {Logger.error(`Fail to backup data.code:${err.code},message:${err.message}`);} else {Logger.info('Succeeded in backing up data.');}});} catch (e) {let error = e as BusinessError;Logger.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);}}) -
使用delete()方法删除数据(模拟意外删除、篡改场景)。
public Delete = (() => {Logger.info('DeleteData start');if (kvStore === undefined) {Logger.info('DeleteData: kvStore not initialized');return;}const KEY_TEST_STRING_ELEMENT = 'key_test_string';try {kvStore.delete(KEY_TEST_STRING_ELEMENT, (err) => {if (err !== undefined) {Logger.error(`Failed to delete data. Code:${err.code},message:${err.message}`);return;}Logger.info('Succeeded in deleting data.');});} catch (e) {let error = e as BusinessError;Logger.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);}}) -
使用restore()方法恢复数据。
public Restore = (() => {Logger.info('Restore start');if (kvStore === undefined) {Logger.info('Restore: kvStore not initialized');return;}let backupFile = 'BK001';try {kvStore.restore(backupFile, (err) => {if (err) {Logger.error(`Fail to restore data. Code:${err.code},message:${err.message}`);} else {Logger.info('Succeeded in restoring data.');}});} catch (e) {let error = e as BusinessError;Logger.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);}}) -
当本地设备存储空间有限或需要重新备份时,还可使用deleteBackup()方法删除备份,释放存储空间。
public DeleteBackup = (() => {Logger.info('DeleteBackup start');if (kvStore === undefined) {Logger.info('DeleteBackup: kvStore not initialized');return;}let backupFile = 'BK001';let files = [backupFile];try {kvStore.deleteBackup(files, (err: BusinessError, data: [string, number][]) => {if (err) {Logger.error(`Failed to delete Backup.code is ${err.code},message is ${err.message}`);} else {Logger.info(`Succeed in deleting Backup.data=${data}`);}});} catch (e) {let error = e as BusinessError;Logger.error(`An unexpected error occurred.code is ${error.code},message is ${error.message}`);}})
关系型数据库备份
数据库操作或者存储过程中,有可能会因为各种原因发生非预期的数据库异常的情况,可以根据需要使用关系型数据库的备份能力,以便在数据库异常时,可靠高效地恢复数据保证业务数据正常使用。
关系型数据库支持手动备份和自动备份(仅系统应用可用)两种方式。
手动备份
手动备份:通过调用backup接口实现数据库手动备份。示例如下:
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { hilog } from '@kit.PerformanceAnalysisKit'
import { UIContext } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
/* context为应用的上下文信息,由调用方自行获取,此处仅为示例。 */
const context = new UIContext().getHostContext() as common.UIAbilityContext;
let store: relationalStore.RdbStore | undefined = undefined;
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S3
};
try {
store = await relationalStore.getRdbStore(context, STORE_CONFIG);
await store.executeSql('CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)');
hilog.info(DOMAIN, 'BackupAndRestore', 'Succeeded in getting RdbStore.');
} catch (e) {
const err = e as BusinessError;
hilog.error(DOMAIN, 'BackupAndRestore', `Failed to get RdbStore. Code:${err.code},message:${err.message}`);
}
if (!store) {
return;
}
try {
/**
* "Backup.db"为备份数据库文件名,默认在RdbStore同路径下备份。
* 也可指定绝对路径:"/data/storage/el2/database/Backup.db",文件路径需要存在,不会自动创建目录。
*/
await store.backup('Backup.db');
hilog.info(DOMAIN, 'BackupAndRestore', `Succeeded in backing up RdbStore.`);
} catch (e) {
const err = e as BusinessError;
hilog.error(DOMAIN, 'BackupAndRestore', `Failed to backup RdbStore. Code:${err.code}, message:${err.message}`);
}
关系型数据库异常重建
在创建或使用关系型数据库的过程中,抛出14800011异常错误码说明数据库出现异常,可以删除数据库后恢复数据。
需要通过在StoreConfig中配置allowRebuild参数为true以设置数据库在出现异常时自动删库。数据库重建成功后为空库,需要开发者重新建表并且使用提前备份好的数据进行数据恢复,备份操作可见关系型数据库备份,数据恢复可见关系型数据库恢复。
若数据库异常前已配置StoreConfig中的allowRebuild为true,则数据库出现异常时将自动删库。
若数据库异常前未配置StoreConfig中的allowRebuild或allowRebuild配置为false,则需将其配置为true再次进行开库。具体示例如下:
let store: relationalStore.RdbStore | undefined = undefined;
/* context为应用的上下文信息,由调用方自行获取,此处仅为示例。 */
const context = new UIContext().getHostContext() as common.UIAbilityContext;
try {
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S3,
allowRebuild: true
};
store = await relationalStore.getRdbStore(context, STORE_CONFIG);
await store.executeSql('CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)');
hilog.info(DOMAIN, 'BackupAndRestore', 'Succeeded in getting RdbStore.');
} catch (e) {
const err = e as BusinessError;
hilog.error(DOMAIN, 'BackupAndRestore', `Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
}
关系型数据库数据恢复
针对数据库出现异常的情况,在数据库重建成功后,需要用提前备份好的数据进行数据恢复。
恢复方式分以下两种,手动备份恢复和自动备份恢复(仅系统应用可用)。
恢复手动备份数据
关系型数据库通过调用backup接口可以实现手动备份数据库,通过restore接口可以实现手动恢复数据库。
具体恢复过程和关键示例代码片段如下,完整示例代码请结合关系型数据库的备份、重建等上下文进行实现。
-
抛出数据库异常错误码。
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');if (store != undefined) {(store as relationalStore.RdbStore).query(predicates, ['ID', 'NAME', 'AGE', 'SALARY', 'CODES']).then((result: relationalStore.ResultSet) => {let resultSet = result;try {/* ...业务的增删改逻辑...*/// 抛出异常if (resultSet?.rowCount == -1) {resultSet?.isColumnNull(0);}// todo resultSet.goToFirstRow()等其它接口也会抛异常while (resultSet.goToNextRow()) {hilog.info(DOMAIN, 'BackupAndRestore', JSON.stringify(resultSet.getRow()));}resultSet.close();} catch (err) {if (err.code === 14800011) {// 执行下文的步骤,即关闭结果集之后进行数据的恢复}hilog.info(DOMAIN, 'BackupAndRestore', JSON.stringify(err));}})} -
关闭所有打开着的结果集。
let resultSets: relationalStore.ResultSet[] = []// 使用resultSet.close()方法关闭所有打开着的结果集for (let resultSet of resultSets) {try {resultSet.close();} catch (e) {if (e.code !== 14800014) {hilog.info(DOMAIN, 'BackupAndRestore', `Code:${e.code}, message:${e.message}`);}}} -
调用restore接口恢复数据。
let store: relationalStore.RdbStore | undefined = undefined;/* context为应用的上下文信息,由调用方自行获取,此处仅为示例。 */const context = new UIContext().getHostContext() as common.UIAbilityContext;let STORE_CONFIG: relationalStore.StoreConfig = {name: 'RdbTest.db',securityLevel: relationalStore.SecurityLevel.S3,allowRebuild: true}try {/*** "Backup.db"为备份数据库文件名,默认在当前 store 所在路径下查找备份文件 Backup.db。* 如在备份时指定了绝对路径:"/data/storage/el2/database/Backup.db", 需要传入绝对路径。*/let backupFilePath = context.databaseDir + '/rdb/Backup.db';const backupExist: boolean = await fileIo.access(backupFilePath);if (!backupExist) {hilog.info(DOMAIN, 'BackupAndRestore', 'Backup is not exist.');// todo 开库建表// todo 自行生成数据return;}} catch (e) {hilog.info(DOMAIN, 'BackupAndRestore', `Code:${e.code}, message:${e.message}`);}try {store = await relationalStore.getRdbStore(context, STORE_CONFIG);// 调用restore接口恢复数据await store.restore('Backup.db');hilog.info(DOMAIN, 'BackupAndRestore', 'Restore from backup success.');} catch (e) {const err = e as BusinessError;hilog.error(DOMAIN, 'BackupAndRestore', `Failed to get RdbStore. Code:${err.code}, message:${err.message}`);}