数据库备份与恢复 (ArkTS)
================

#### 场景介绍

如果操作或存储的过程中出现问题，开发者可以使用恢复功能，将数据库恢复到之前的状态，重新对数据库进行操作。

在数据库被篡改、删除、或者设备断电场景下，数据库可能会因为数据丢失、数据损坏、脏数据等而不可用，可以通过数据库的备份恢复能力将数据库恢复至可用状态。

键值型数据库和关系型数据库均支持对数据库的备份和恢复。另外，键值型数据库还支持删除数据库备份，以释放本地存储空间。

#### 键值型数据库备份、恢复与删除

键值型数据库，通过backup接口实现数据库备份，通过restore接口实现数据库恢复，通过deletebackup接口删除数据库备份。具体接口及功能，可见[分布式键值数据库](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-distributedkvstore)。

1.  创建数据库。
    
    (1) 创建kvManager。
    
    (2) 配置数据库参数。
    
    (3) 创建kvStore。
    
    // 导入模块
    // 在pages目录下新建KvStoreInterface.ets
    import { 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}\`);
      }
    })
    
2.  使用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}\`);
      }
    })
    
3.  使用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}\`);
      }
    })
    
4.  使用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}\`);
      }
    })
    
5.  使用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}\`);
      }
    })
    
6.  当本地设备存储空间有限或需要重新备份时，还可使用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}\`);
      }
    })
    

#### 关系型数据库备份

数据库操作或者存储过程中，有可能会因为各种原因发生非预期的数据库异常的情况，可以根据需要使用关系型数据库的备份能力，以便在数据库异常时，可靠高效地恢复数据保证业务数据正常使用。

关系型数据库支持手动备份和自动备份（仅系统应用可用）两种方式。

#### \[h2\]手动备份

手动备份：通过调用[backup](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-data-relationalstore-rdbstore#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](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-data-relationalstore-i#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}\`);
}

#### 关系型数据库数据恢复

针对数据库出现异常的情况，在数据库重建成功后，需要用提前备份好的数据进行数据恢复。

恢复方式分以下两种，手动备份恢复和自动备份恢复（仅系统应用可用）。

#### \[h2\]恢复手动备份数据

关系型数据库通过调用backup接口可以实现[手动备份数据库](#手动备份)，通过restore接口可以实现手动恢复数据库。

具体恢复过程和关键示例代码片段如下，完整示例代码请结合关系型数据库的备份、重建等上下文进行实现。

1.  抛出数据库异常错误码。
    
    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));
          }
        })
    }
    
2.  关闭所有打开着的结果集。
    
    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}\`);
        }
      }
    }
    
3.  调用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}\`);
    }
    

#### 示例代码

*   [数据库的备份与恢复](https://gitcode.com/HarmonyOS_Samples/data-base-upgrade)