harmony 鸿蒙数据库备份与恢复(ArkTS)

  • 2023-06-24
  • 浏览 (1902)

数据库备份与恢复(ArkTS)

场景介绍

当应用在处理一项重要的操作,显然是不能被打断的。例如:写入多个表关联的事务。此时,每个表的写入都是单独的,但是表与表之间的事务关联性不能被分割。

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

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

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

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

键值型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复,通过deletebackup接口删除数据库备份。具体接口及功能,可见分布式键值数据库

  1. 创建数据库。

(1) 创建kvManager。

(2) 配置数据库参数。

(3) 创建kvStore。

   import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
   import { hilog } from '@kit.PerformanceAnalysisKit';
   import { distributedKVStore } from '@kit.ArkData';
   import { BusinessError } from '@kit.BasicServicesKit';

   export default class EntryAbility extends UIAbility {
     onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
       this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
       hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
       let kvManager: distributedKVStore.KVManager;
       let kvStore: distributedKVStore.SingleKVStore|undefined = undefined;
       let context = this.context;
       const kvManagerConfig: distributedKVStore.KVManagerConfig = {
         context: context,
         bundleName: 'com.example.datamanagertest'
       }
       try {
         kvManager = distributedKVStore.createKVManager(kvManagerConfig);
         console.info('Succeeded in creating KVManager.');
         try {
           const options: distributedKVStore.Options = {
             createIfMissing: true,
             encrypt: true,
             backup: false,
             autoSync: false,
             kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
             securityLevel: distributedKVStore.SecurityLevel.S3
           };
           kvManager.getKVStore<distributedKVStore.SingleKVStore>('storeId', options, (err, store: distributedKVStore.SingleKVStore) => {
             if (err) {
               console.error(`Failed to get KVStore. Code:${err.code},message:${err.message}`);
               return;
             }
             console.info('Succeeded in getting KVStore.');
             kvStore = store;
           });
         } catch (e) {
           let error = e as BusinessError;
           console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
         }
       } catch (e) {
         let error = e as BusinessError;
         console.error(`Failed to create KVManager. Code:${error.code},message:${error.message}`);
       }

       if (kvStore !== undefined) {
         kvStore = kvStore as distributedKVStore.SingleKVStore;
         //进行后续操作
         //...
       }
     }
   }
  1. 使用put()方法插入数据。
   const KEY_TEST_STRING_ELEMENT = 'key_test_string';
   const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
   try {
     kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
       if (err !== undefined) {
         console.error(`Fail to put data. Code:${err.code},message:${err.message}`);
         return;
       }
       console.info('Succeeded in putting data.');
     });
   } catch (e) {
     let error = e as BusinessError;
     console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
   }
  1. 使用backup()方法备份数据。
   let backupFile = 'BK001';
   try {
     kvStore.backup(backupFile, (err) => {
       if (err) {
         console.error(`Fail to backup data.code:${err.code},message:${err.message}`);
       } else {
         console.info('Succeeded in backupping data.');
       }
     });
   } catch (e) {
     let error = e as BusinessError;
     console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
   }
  1. 使用delete()方法删除数据(模拟意外删除、篡改场景)。
   try {
     kvStore.delete(KEY_TEST_STRING_ELEMENT, (err) => {
       if (err !== undefined) {
         console.error(`Fail to delete data. Code:${err.code},message:${err.message}`);
         return;
       }
       console.info('Succeeded in deleting data.');
     });
   } catch (e) {
     let error = e as BusinessError;
     console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
   }
  1. 使用restore()方法恢复数据。
   try {
     kvStore.restore(backupFile, (err) => {
       if (err) {
         console.error(`Fail to restore data. Code:${err.code},message:${err.message}`);
       } else {
         console.info('Succeeded in restoring data.');
       }
     });
   } catch (e) {
     let error = e as BusinessError;
     console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
   }
  1. 当本地设备存储空间有限或需要重新备份时,还可使用deleteBackup()方法删除备份,释放存储空间。
   let files = [backupFile];
   try {
     kvStore.deleteBackup(files).then((data) => {
       console.info(`Succeed in deleting Backup. Data:filename is ${data[0]},result is ${data[1]}.`);
     }).catch((err: BusinessError) => {
       console.error(`Fail to delete Backup. Code:${err.code},message:${err.message}`);
     })
   } catch (e) {
     let error = e as BusinessError;
     console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
   }

关系型数据库备份

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

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

手动备份

手动备份:通过调用backup接口实现数据库手动备份。示例如下:

import { UIAbility } from '@kit.AbilityKit';
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  async onCreate(): Promise<void> {
    let store: relationalStore.RdbStore|undefined = undefined;
    let context = this.context;

    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: 'RdbTest.db',
      securityLevel: relationalStore.SecurityLevel.S3,
      allowRebuild: true
    };
    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)');
      console.info('Succeeded in getting RdbStore.');
    } catch (e) {
      const err = e as BusinessError;
      console.error(`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");
      console.info(`Succeeded in backing up RdbStore.`);
    } catch (e) {
      const err = e as BusinessError;
      console.error(`Failed to backup RdbStore. Code:${err.code}, message:${err.message}`);
    }
  }
}

自动备份(仅系统应用可用)

自动备份:可以通过在StoreConfig中配置haMode参数为MAIN_REPLICA实现数据库双写备份,仅支持系统应用。示例如下:

import { UIAbility } from '@kit.AbilityKit';
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  async onCreate(): Promise<void> {
    let store: relationalStore.RdbStore|undefined = undefined;
    let context = this.context;
    try {
      // 配置StoreConfig的haMode参数为MAIN_REPLICA。
      const AUTO_BACKUP_CONFIG: relationalStore.StoreConfig = {
        name: "BackupRestoreTest.db",
        securityLevel: relationalStore.SecurityLevel.S3,
        haMode: relationalStore.HAMode.MAIN_REPLICA, // 配置为双写备份
        allowRebuild: true
      }

      // 使用getRdbStore()方法创建关系型数据库。
      store = await relationalStore.getRdbStore(context, AUTO_BACKUP_CONFIG);
      console.info('Succeeded in getting RdbStore.');
    } catch (e) {
      const err = e as BusinessError;
      console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
    }
  }
}

关系型数据库异常重建

在创建或使用关系型数据库的过程中,抛出14800011异常错误码说明数据库出现异常,可以删除数据库后恢复数据。

需要通过在StoreConfig中配置allowRebuild参数为true以设置数据库在出现异常时自动删库。数据库重建成功后为空库,需要开发者重新建表并且使用提前备份好的数据进行数据恢复,备份操作可见关系型数据库备份,数据恢复可见关系型数据库恢复

若数据库异常前已配置StoreConfig中的allowRebuild为true,则数据库出现异常时将自动删库。

若数据库异常前未配置StoreConfig中的allowRebuild或allowRebuild配置为false,则需将其配置为true再次进行开库。具体示例如下:

import { UIAbility } from '@kit.AbilityKit';
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  async onCreate(): Promise<void> {
    let store: relationalStore.RdbStore|undefined = undefined;
    let context = this.context;
    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)');
      console.info('Succeeded in getting RdbStore.');
    } catch (e) {
      const err = e as BusinessError;
      console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
    }
  }
}

关系型数据库数据恢复

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

恢复方式分以下两种,手动备份恢复和自动备份恢复(仅系统应用可用)。

恢复手动备份数据

关系型数据库通过调用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(), resultSet.count等其它接口也会抛异常
         while (resultSet.goToNextRow()) {
           console.info(JSON.stringify(resultSet.getRow()))
         }
         resultSet.close();
       } catch (err) {
           if (err.code === 14800011) {
              // 执行下文的步骤,即关闭结果集之后进行数据的恢复
           }
           console.error(JSON.stringify(err));
       }
     })
   }
  1. 关闭所有打开着的结果集。
   // 获取所有打开着的结果集
   let resultSets: Array<relationalStore.ResultSet> = [];
   // 使用resultSet.close()方法关闭所有打开着的结果集
   for (let resultSet of resultSets) {
     try {
       resultSet.close();
     } catch (e) {
         if (e.code !== 14800014) {
           console.error(`Code:${e.code}, message:${e.message}`);
         }
     }
   }
  1. 调用restore接口恢复数据。
   import { UIAbility } from '@kit.AbilityKit';
   import { relationalStore } from '@kit.ArkData';
   import { BusinessError } from '@kit.BasicServicesKit';
   import { fileIo } from '@kit.CoreFileKit';

   export default class EntryAbility extends UIAbility {
     async onCreate(): Promise<void> {
       let store: relationalStore.RdbStore|undefined = undefined;
       let context = this.context;
       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 = await fileIo.access(backupFilePath);
         if (!backupExist) {
           console.info("Backup is not exist.");
           // todo 开库建表
           // todo 自行生成数据
           return;
         }
       } catch (e) {
         console.error(`Code:${e.code}, message:${e.message}`);
       }

       try {
         store = await relationalStore.getRdbStore(context, STORE_CONFIG);
         // 调用restore接口恢复数据
         await store.restore("Backup.db");
         console.log("Restore from back success.")
       } catch (e) {
         const err = e as BusinessError;
         console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
       }
     }
   }

恢复自动备份数据(仅系统应用可用)

关系型数据库,可以通过restore接口恢复自动备份的数据,仅支持系统应用。

关键示例代码片段如下,完整示例代码请结合关系型数据库的备份、重建等上下文进行实现。

   if (store !== undefined) {
     try {
       // 增删改查
     } catch (err) {
         if (err.code == 14800011) {
           // 获取所有打开着的结果集
           let resultSets: Array<relationalStore.ResultSet> = [];
           // 使用resultSet.close()方法关闭所有打开着的结果集
           for (let resultSet of resultSets) {
             try {
               resultSet.close();
             } catch (e) {
                 if (e.code !== 14800014) {
                   console.error(`Code:${e.code}, message:${e.message}`);
                 }
             }
           }
   
           (store as relationalStore.RdbStore).restore("Backup.db", (err: BusinessError) => {
             if (err) {
               console.error(`Failed to restore RdbStore. Code:${err.code}, message:${err.message}`);
               return;
             }
             console.info(`Succeeded in restoring RdbStore.`);
           })
         }
         console.error(`Code:${err.code}, message:${err.message}`);
     }
   }

你可能感兴趣的鸿蒙文章

harmony 鸿蒙ArkData(方舟数据管理)

harmony 鸿蒙基于设备分类和数据分级的访问控制(ArkTS)

harmony 鸿蒙应用数据向量化

harmony 鸿蒙应用数据持久化概述

harmony 鸿蒙基于标准化数据结构的控件

harmony 鸿蒙数据库加密(ArkTS)

harmony 鸿蒙ArkData简介

harmony 鸿蒙通过键值型数据库实现数据持久化(ArkTS)

harmony 鸿蒙通过用户首选项实现数据持久化 (ArkTS)

harmony 鸿蒙通过关系型数据库实现数据持久化(ArkTS)

0  赞