HarmonyOS开发(九):数据管理

news2024/11/26 11:58:03

1、概述

1.1、功能简介

数据管理为开发者提供数据存储、数据管理能力。

它分为两个部分:

  • 数据存储:提供通用数据持久化能力,根据数据特点,分为用户首选项、键值型数据库和关系型数据库。
  • 数据管理:提供高效的数据管理能力,包括权限管理、数据备分恢复、数据共享等

注:应用创建的数据库,都保存到应用少盒,当应用卸载时,数据库也会自动删除。

1.2、运作机制

数据管理模块包括用户首选项、键值型数据管理、关系型数据管理、分布式数据对象和跨应用数据管理。

Interface接口层提供标准JS API接口

Frameworks&System service层负责实现部件数据存储功能

另外还有一些SQLite和其它子系统的依赖

  • 用户首选项:Preferences, 提供轻量级配置数据持久化能力,支持订阅数据变化的通知能力。不支持分布式同步,常常用来保存应用配置信息、用户偏好设置等
  • 键值型数据管理:KV-Store,提供了键值型数据库的读写、加密、手动备份能力。暂不支持分布式功能。
  • 关系型数据管理:RelationalStore,提供了关系型数据库的增删改查、加密、手动备份能力。暂不支持分布式功能。
  • 分布式数据对象:DataObject,独立提供地象型结构数据的分布式能力,暂不支持分布式功能。
  • 跨应用数据管理:DataShare,提供了向其他应用共享以及管理其数据的方法。仅系统应用可用。

2、应用数据持久化概述

应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。

HarmonyOS标准系统支持典型的存储数据形态有:用户首选项、键值型数据库、关系型数据库

3、用户首选项实现数据持久化

用户首选项为应用提供key-value键值型的数据处理能力,支持应用持久化轻量级数据,并对其进行修改和查询。

Preferences不适合存放过多数据,适用的场景一般为应用保存用户个性化的配置。

3.1、运作机制

用户程序通过JS接口调用用户首选项读写对应的数据文件。开发者可以把用户首选项持久化文件的内容加载到Preferences实例,每个文件唯一对应到一个Preferences实例,系统会通过静态容器把这个实例存储在内存中,直到主动从内存中移除这个实例或删除这个文件。

3.2、使用约束

1、key键为string类型,要求非空且长度不超过80个字节

2、value值为string类型时可以为空,不为空时长度不超过8192个字节

3、内存会随着存储数据量的增大而增大,所以存储的数据应该是轻量级的,建议存储的数据不要超过1万条

3.3、相关接口说明

接口大部分都是异步接口,异步接口都有callback和Promise两种返回形式。以下为callback为例说明

接口名称描述
getPreferences(context:Context,name:string,callback:AsyncCallback<Preferences>):void获取Preferences 实例
put(key:string,value:valueType,callback:AsyncCallback<void>):void把数据写入Preferences实例,可以通过flush把实例进行持 久化
has(key:string,callback:AsyncCallback<boolean>):void检查实例中是否包含指定的key的存储键值对,给定的key不可以为空
get(key:string,defValue:valueType,callback:AsyncCallback<valueType>):void获取指定键对应的值,如果值为null或者非默认值类型,返回默认数据defValue
delete(key:string,callback:AsyncCallback<void>):void从实例中删除指定key的存储键值对
flush(callback:AsyncCallback<void>):void把当前实例中的数据异步存储到用户首选项持久化文件中
on(type:'change',callback?:Callback<{key:string}>):void订阅数据变更,订阅的key的值发生变化,在执行flush方法后,触发callback回调
off(type:'change',callback?:Callback<{key:string}>):void取消订阅数据变更
deletePreferences(context:Context,name:string,callback:AsyncCallback<void>):void从内存中移除指定实例,如果这个实例有对应的持久化文件则同时会把持外化文件删除

3.4、开发步骤

1、导入用户首选项模块

import dataPreferences from '@ohos.data.preferences';

 2、获取Preferences实例,读取指定文件,把数据加载到Preferences实例,用于数据操作

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import dataPreferences from '@ohos.data.preferences';

export default class EntryAbility extends UIAbility {

  onCreate(want, launchParam) {

  }

  onDestroy() {

  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    try{
      dataPreferences.getPreferences(this.context,'mystore',(err,preferences) => {
        if(err) {
          console.error(`Failed to get preferences. Code:${err.code},Message:${err.message}`);
          return;
        }
        console.info('Succeeded in getting preferences.')
        // 进行相关的数据操作
      })
    } catch (err) {
      console.error(`Failed to get preferences. Code:${err.code},Message:${err.message}`);
    }

    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
    });
  }

  onWindowStageDestroy() {
    // Main window is destroyed, release UI related resources

  }

  onForeground() {
    // Ability has brought to foreground

  }

  onBackground() {
    // Ability has back to background

  }
}

在onWindowStageCreate方法中进行读取

3、写入数据

使用put()方法保存数据到缓存的Preferences实例中,在写入数据后如果有必要可以使用flush()方法把Preferences实例的数据存储到持久化文件。

注意:如果键此时已存在则会修改值,如果需要是在键不存在时新增键值对,则需要使用has()进行检查

4、读取数据

使用get()方法获取数据,如果值为null或者非默认值类型,则返回默认数据。

5、删除数据

使用delete()方法删除指定的键值对

6、据的持久化

应用存入数据到Preferences后,可以使用flush()方法实现数据持久化

7、订阅数据更新

使用on(),订阅key值发生变化,flush()执行时,会回调其中的回调方法

8、删除指定文件

使用deletePreferences()方法从内存中移除指定文件对应的Preferences实例,包括内存中的数据。若该Preference存在对应的持久化文件,则同时删除该持久化文件,包括指定文件及其备份文件、损坏文件。

// EntryAbility.ts

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import dataPreferences from '@ohos.data.preferences';

export default class EntryAbility extends UIAbility {

  onCreate(want, launchParam) {

  }

  onDestroy() {

  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    try{
      dataPreferences.getPreferences(this.context,'mystore',(err,preferences) => {
        if(err) {
          console.error(`Failed to get preferences. Code:${err.code},Message:${err.message}`);
          return;
        }
        console.info('Succeeded in getting preferences.')
        // 进行相关的数据操作
        globalThis.dataPreferences = preferences;
      })
    } catch (err) {
      console.error(`Failed to get preferences. Code:${err.code},Message:${err.message}`);
    }

    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
    });
  }

  onWindowStageDestroy() {
    // Main window is destroyed, release UI related resources

  }

  onForeground() {
    // Ability has brought to foreground

  }

  onBackground() {
    // Ability has back to background

  }
}
// index.ets

import dataPreferences from '@ohos.data.preferences';
import Prompt from '@system.prompt';
import common from '@ohos.app.ability.common';

@Entry
@Component
struct Index {
  @State message: string = '';
  @State message1: string = '';
  dpf:dataPreferences.Preferences = globalThis.dataPreferences;

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .margin({bottom:10})
        Text(this.message1)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .margin({bottom:10})
        Button('put')
          .margin({bottom:15})
          .onClick(() => {
            try{
              this.dpf.has('startup', (err,val) => {
                if(err) {
                  console.error(`Failed to check data. Code:${err.code}, message:${err.message}`);
                  return;
                }
                if (val) {
                  console.info('startup is exists!!')
                } else {
                  try {
                    this.dpf.put('startup', 'auto', (err) => {
                      if (err) {
                        console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
                        return;
                      }
                      console.info('succeeded in putting data.')
                    })
                  } catch (err) {
                    console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
                  }
                }
              })
            }catch(err) {
              console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
            }
          })
        Button('get')
          .margin({bottom:15})
          .onClick(() => {
            try{
              this.dpf.get('startup','default',(err,val) => {
                if(err) {
                  console.error(`Failed to get data. Code:${err.code}, message:${err.message}`);
                }
                console.info(`succeeded in getting value of 'startup'. val:${val} `);
                this.message = val.toString();
              })
            }catch (err) {
              console.error(`Failed to get data. Code:${err.code}, message:${err.message}`);
            }
          })
        Button('delete')
          .margin({bottom:15})
          .onClick(() => {
            try{
              this.dpf.delete('startup', (err) => {
                if(err) {
                  console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
                  return;
                }
                console.info('succeeded in deleting the data')
              })
            } catch (err) {
              console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
            }
          })
        Button('flush')
          .margin({bottom:15})
          .onClick(() => {
            try{
              this.dpf.flush((err) => {
                console.error(`Failed to flush data. Code:${err.code}, message:${err.message}`);
                return;
              })
              Prompt.showToast({
                message: '数据持久化成功!',
              })
            } catch (err) {
              console.error(`Failed to flush data. Code:${err.code}, message:${err.message}`);
            }
          })
        Button('订阅')
          .margin({bottom:15})
          .onClick(() => {
            this.dpf.on('change',(key) => {
              console.info(key + '数据发生变化')
            })
          })
        Button('更新')
          .margin({bottom:15})
          .onClick(() => {
            try{
              this.dpf.put('startup','manual',(err) => {
                if(err) {
                  console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
                  return;
                }
              })
            } catch (err){
              console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
            }
          })
        Button('deletePreferences')
          .onClick(() => {
            try{
              let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
              dataPreferences.getPreferences(context,'mystore', (err, val) => {
                if(err) {
                  console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
                  return;
                }
                console.info(`successed to delete preferences.`);
              })
            } catch (err) {
              console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
            }
          })
      }
      .width('100%')
    }
    .height('100%')
  }
  
}

4、键值型数据库实现数据持久化

4.1、使用场景介绍

键值型数据库存储键值对形式的数据,一般用来存储的数据没有复杂的关系模型。

4.2、使用约束

1、设备协同数据库,针对每条记录,key的长度小于等于896Byte,value小于4MB

2、单版本数据库,针对每条记录,key的长度小于等于1KB,value小于4MB

3、每个应用程序最多支持同时打开16个键值型分布式数据库

4、键值型数据库事件回调方法不允许进行阻塞操作

4.3、相关接口说明

接口大部分是异步操作,异步接口都有callback和Promise两种形式

接口名称描述
createKVManager(config: KVManagerConfig): KVManager创建一个KVManager对象实例,用于管数据库对象
getKVStore<T>(storeId:string,options:Options,callback:AsyncCallback<T>):void指定Options和storeId,创建并得到指定类型的KVStore数据库
put(key:string,value:Uint8Array|string|number|boolean,callback:AsyncCallback<void>):void添加指定类型的键值对到数据库
get(key:string,callback:AsyncCallback<Uint8Array|string|number|boolean>):void 获取指定键对应的值
delete(key:string,callback:AsyncCallback<void>):void从数据库中删除指定键值数据

4.4、开发步骤

 1、获取一个KVManager实例

在EntryAbility.ts中onCreate()或onWindowStageCreate()中创建。

onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    // KVManagerConfig
    let context = this.context;
    const KvManagerConfig = {
      context: context,
      bundleName: 'com.xiaoxie'
    };
    try{
      let kvManager = distributedKVStore.createKVManager(KvManagerConfig);
      console.info('Succeeded in create KVManager.')
      globalThis.kvManager = kvManager;
    } catch (err){
      console.error(`Failed to create KVManager, Code:${err.code},Message:${err.Message}`);
    }


    windowStage.loadContent('pages/KVStore', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
    });
  }

2、创建并获取键值数据库

3、调用put()方法向键值数据库中插入数据,当我们据时key值存在则会修改其值,否则新增一条数据

4、调用get()方法获取指定键值

5、调用delete()方法删除指定键值的数据

// KVStore.ets
import distributedKVStore from '@ohos.data.distributedKVStore';
import Prompt from '@system.prompt';

const options = {
  createIfMissing: true,  // 当数据库文件不存在的时候是否创建,默认创建
  encrypt: false, // 设置数据库文件是否加密,默认不加密
  backup: false, // 设置数据库文件是否备份,默认备份
  kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION, // 设置要创建数据库的类型,默认为多设备协同
  securityLevel: distributedKVStore.SecurityLevel.S2  // 设置数据库的安全级别
};

@Entry
@Component
struct KVStore {
  @State message: string = '';
  kvManager:distributedKVStore.KVManager = globalThis.kvManager
  kvStore:distributedKVStore.SingleKVStore = undefined;


  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .margin({bottom:10})

        Button('1、创建并获取键值数据库')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            try{
              this.kvManager.getKVStore('storeId',options,(err,kvStore:distributedKVStore.SingleKVStore) => {
                if(err) {
                  console.error(`Failed to get KVStore, Code:${err.code},Message:${err.message}`);
                  return;
                }
                Prompt.showToast({
                  message: '获取键值数据库成功!',
                  duration: 1500
                })
                this.kvStore = kvStore;
              });
            } catch (e) {
              console.error(`Failed to get KVStore, Code:${e.code},Message:${e.message}`);
            }
          })

        Button('2、向键值数据库插入数据')
          .width('50%')
          .margin({bottom: 10})
          .onClick(() => {
            try{
              if(this.kvStore) {
                this.kvStore.put('test_key','test_value',(err) => {
                  if(err) {
                    console.error(`Failed to put data, Code:${err.code},Message:${err.message}`);
                    return;
                  }
                  Prompt.showToast({
                    message: '向键值数据加中插入数据成功!',
                    duration: 1500
                  })
                })
              } else {
                Prompt.showToast({
                  message: '错误:请先获取数据库!',
                  duration: 1500
                })
              }
            } catch (e) {
              console.error(`Failed to put data, Code:${e.code},Message:${e.message}`);
            }
          })

        Button('3、获取指定键的值')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            try{
              if(this.kvStore) {
                this.kvStore.get('test_key',(err,data) => {
                  if(err) {
                    this.message = '';
                    console.error(`Failed to get data, Code:${err.code},Message:${err.message}`);
                    return;
                  }
                  this.message = '';
                  this.message = data.toString();
                })
              } else {
                Prompt.showToast({
                  message: '错误:请先获取数据库!',
                  duration: 1500
                })
              }
            } catch (e) {
              console.error(`Failed to get data, Code:${e.code},Message:${e.message}`);
            }
          })

        Button('4、删除指定键值数据')
          .width('50%')
          .onClick(() => {
            try{
              if(this.kvStore) {
                this.kvStore.delete('test_key',(err) => {
                  if(err) {
                    console.error(`Failed to delete data, Code:${err.code},Message:${err.message}`);
                    return;
                  }
                  Prompt.showToast({
                    message: '删除指定键值数据成功!',
                    duration: 1500
                  })
                })
              } else {
                Prompt.showToast({
                  message: '错误:请先获取数据库!',
                  duration: 1500
                })
              }
            } catch (e){
              console.error(`Failed to delete data, Code:${e.code},Message:${e.message}`);
            }
          })

      }
      .width('100%')
    }
    .height('100%')
  }
}

5、关系型数据库实现数据持久化

5.1、使用场景介绍

关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景。

5.2、相关术语介绍

1、谓词:数据库中用来代表数据实体的性质、特征或数据实体之间关系的词项,它主要用来定义数据库的操作条件。

2、结果集:指的是用户查询后的结果集合。

5.3、动作机制

关系型数据库对应用提供通用的操作接口,底层使用SQLite作为持久化存储引擎。

5.4、约束限制

1、系统默认日志方式是WAL(Write Ahead Log)模式,默认落盘方式是FULL模式

2、连接池最大个数是4个

3、数据库同一时间只能支持一个写操作

4、当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动清除

5.5、相关接口说明

大部分为异步接口,异步接口都有callback和Promise两种形式

接口说明描述
getRdbStore(context: Context, config: StoreConfig, callback:AsyncCallback<RdbStore>): void获得一个RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口来执行相关的数据操作
executeSql(sql: string, bindArgs:Array<ValueType>,callback:AsyncCallback<void>): void执行包含指定参数但是不返回值的sql语句
inser(table:string,values:valuesBucket,callback:AsyncCallback<number>): void向表中插入一行数据
update(values:ValuesBucket,predicates:RdbPredicates,callback:AsyncCallback<number>): void根据RdbPredicates的指定实例对象更新数据库中数据
delete(predicates:RdbPredicates,callback:AsyncCallback<number>):void根据RdbPredicates的指定实例对象删除数据库中数据
query(predicates:RdbPredicates,columns:Array<string>,callback:AsyncCallback<ResultSet>):void根据指定条件查询数据库中的数据
deleteRdbStore(context:Context,name:string,callback:AsyncCallback<void>):void删除数据库

5.6、开发步骤

 1、获取一个RdbStore

在EntryAbility.ts文件件的onCreate()或onWindowStageCreate()方法中创建数据库获取到RdbStore对象并绑定到globalThis,便于后续使用。

// EntryAbility.ts
onWindowStageCreate(windowStage: window.WindowStage) {

    // RdbStore
        const STORE_CONFIG = {
          name: 'RdbTest.db', // 数据库名称
          securityLevel: relationalStore.SecurityLevel.S1 // 数据库的安全级别
        };

        relationalStore.getRdbStore(this.context, STORE_CONFIG, (err,store) => {
          if(err) {
            console.error(`Failed to create rdbstore. Code:${err.code},Message:${err.message}`);
            return;
          }
          console.info('Succeeded in create RdbStore.')
          globalThis.rdbStore = store;
    })
}

2、创建数据表,定义好建表语句,执行executeSql()方法

3、调用insert()方法来插入数据

注意:关系型数据库没有显式的flush操作实现持久化,数据插入即保存在持久化文件

4、根据谓词指定的实例对象,对数据进行修改或删除

5、根据谓词指定的查询条件查找数据,调用query()方法查找数据,会返回一个ResultSet结果集,当应用完成查询数据操作,不再使用ResultSet时,及时调用close方法关闭结查集,释放内存空间。

5、删除数据库,调用deleteRdbStore()方法,用来删除数据库及数据库相关的文件

import relationalStore from '@ohos.data.relationalStore'
import Prompt from '@system.prompt';
import common from '@ohos.app.ability.common';

@Entry
@Component
struct RdbStore {
  @State message: string = ''
  rdbStore: relationalStore.RdbStore = globalThis.rdbStore;


  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .margin({bottom:10})

        Button('创建数据表')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            try {
              // 数据库建表语句
              let SQL_CREATE_TABLE: string = 'create table if not exists employee(id integer primary key autoincrement, name text not null, age integer, salary real, codes blob)';
              this.rdbStore.executeSql(SQL_CREATE_TABLE); // 执行sql语句,创建数据表
              Prompt.showToast({
                message: '创建数据表成功',
                duration: 1500
              })
            } catch (err) {
              console.error(`创建数据表失败,Code:${err.code},Message:${err.Message}`);
            }
          })

        Button('向数据表中插入数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            let valueBucket = {
              'name': '张三',
              'age': 18,
              'salary': 20000.00,
              'codes' : new Uint8Array([1,2,3,4,5])
            };
            this.rdbStore.insert('employee',valueBucket,(err,rowId) => {
              if(err) {
                console.error(`Failed to insert data. Code:${err.code},Message:${err.message}}`);
                return;
              }
              Prompt.showToast({
                message: `插入数据成功,插入数据id:${rowId}`,
                duration: 2000
              })
            })
          })

        Button('修改数据表中指定数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            let valueBucket = {
              'name': '李四',
              'age': 18,
              'salary': 20000.00,
              'codes' : new Uint8Array([1,2,3,4,5])
            };
            let predicates = new relationalStore.RdbPredicates('employee'); // 创建表employee的predicates
            predicates.equalTo('name','张三');  // 匹配employee中name为张三的字段
            this.rdbStore.update(valueBucket,predicates,(err,rows) => {
              if(err) {
                console.error(`Failed to update data. Code:${err.code},Message:${err.message}}`);
                return;
              }
              Prompt.showToast({
                message: `修改数据成功,修改影响记录行数:${rows}`,
                duration: 2000
              })
            })
          })

        Button('删除数据表中指定数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            let predicates = new relationalStore.RdbPredicates('employee');
            predicates.equalTo('name','李四');
            this.rdbStore.delete(predicates, (err,rows) => {
              if(err) {
                console.error(`Failed to delete data. Code:${err.code},Message:${err.message}}`);
                return;
              }
              Prompt.showToast({
                message: `删除数据成功,删除影响记录行数:${rows}`,
                duration: 2000
              })
            })
          })

        Button('查询数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            this.message = '';
            let predicates = new relationalStore.RdbPredicates('employee');
            predicates.equalTo('name','张三');
            this.rdbStore.query(predicates,['id','name','age','salary','codes'],(err,resultSet) => {
              if(err) {
                this.message = '';
                console.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
                return;
              }
              // this.message = resultSet.columnNames.join('|')
              if(resultSet.rowCount > 0 ){
                resultSet.goToFirstRow();
                this.message = resultSet.getString(resultSet.getColumnIndex('name')) + ' - ' + resultSet.getDouble(resultSet.getColumnIndex('salary'));
              }
              resultSet.close();
            })
          })

        Button('删除数据库及相关文件')
          .width('50%')
          .onClick(() => {
            let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
            relationalStore.deleteRdbStore(context,'RdbTest.db',(err) => {
              if(err) {
                console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
                return;
              }
              Prompt.showToast({
                message: '删除数据库成功!',
                duration: 1500
              })
            })
          })

      }
      .width('100%')
    }
    .height('100%')
  }
}

6、数据可靠性与安全性

6.1、功能场景说明

在系统运行中,存储损坏、存储空间不足、文件系统权限、系统掉电等等都可能导致数据库发生故障。为此数据管理提供了数据可靠与安全性相关的解决方案和保障。

  • 备份、恢复功能:重要业务数据丢失出息严重异常场景,可以通过备份恢复数据库,保障数据不丢失
  • 数据库加密功能:当数据库中存储一些敏感信息时,可以对数据库进行加密,提高数据的安全性
  • 数据库分类分级:提供基于数据安全标签和设备安全等级进行访问控制的能力,保证数据安

另: 备份数据库存储在应用的沙箱中,当存储空间不足时,可以选择删除本地的数据库备份,释放空间。

6.2、基本概念

6.2.1、数据库备份与恢复

  • 数据库备份:对当前数据库的数据库文件进行完整备份,在做备份时不用关闭数据库,直接调用对应的备份接口就可以了
  • 数据库恢复:从指定的备份文件恢复到当前数据库文件。恢复完后,当前数据库数据保持与指定备份文件一致

6.2.2、数据库加密

加密是对整个数据库文件的加密,可以增强数据库的安全性,有效保护数据库内容

6.2.3、数据库分类分级

分布式数据管理对数据实施分类分级保护,提供基于数据安全标签以及设备安全等级的访问控制机制。数据安全标签和设备安全等级越高,加密措施和访问控制措施越严格,数据安全性越高。

6.3、运作机制

6.3.1、数据库备份与恢复机制

数据库在备份时,会把当前数据库备份在指定的文件件中,后续对数据库的操作不会影响备份的数据库文件,只有当恢复指定的数据库文件时,才会把备份的数据库文件件覆盖当前数据库,实现回滚。

键值型数据库备份路径:/data/service/el1(el2)/public/database/...{appId}/kvdb/backup/...{storeId}

关系型数据库备份路径:/data/app/el1(el2)/100/database/...{bundlename}/rdb

6.3.2、数据库加密机制

在数据库加密时,开发者无需传入密钥,只需要设置数据库加密的状态即可。系统会自动帮助开发者把数据库加密。使用huks通用密钥库系统。

6.4、相关约束限制

数据库加密密钥一年自动更换一次

键值型数据库最多可以备份5份

键值型数据库的自动备份需要在熄屏且充电的状态下进行

6.5、数据库备份与恢复

键值型数据库和关系型数据库都支持数据库的备份与恢复。同时键值型数据库还可以删除数据库备份,以释放本地存储空间。

6.5.1、键值型数据库的备份、恢复及删除

键值型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复,通过deletebackup接口删除数据库备份。


import distributedKVStore from '@ohos.data.distributedKVStore';
import Prompt from '@system.prompt';
const options = {
  createIfMissing: true,
  encrypt: false,
  backup: false,
  kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
  securityLevel: distributedKVStore.SecurityLevel.S2
};

@Entry
@Component
struct KVStoreBack {
  @State message: string = '';
  kvManager: distributedKVStore.KVManager = globalThis.kvManager;
  kvStore: distributedKVStore.SingleKVStore;

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .margin({bottom:10})
        Button('1.创建并获取键值数据库')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            let kvStorePromise = this.kvManager.getKVStore("myStore",options);
            kvStorePromise.then((store: distributedKVStore.SingleKVStore) => {
              this.kvStore = store;
            }).catch((err) => {
              console.error(`创建并获取键值数据库失败, 失败代码:${err.code}, 失败原因:${err.message}`);
              return;
            })
          })
        Button('2.键值数据库插入数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            try {
              this.kvStore.put('name', 'xiaoxie', (err) => {
                if (err) {
                  console.error(`存储数据失败, 失败代码:${err.code}, 失败原因:${err.message}`);
                  return;
                }
                Prompt.showToast({
                  message: '存储数据成功!!',
                  duration: 1500
                })
                this.message = 'xiaoxie'; // 添加数据成功把message赋值为写入的值
              })
            } catch (e) {
              console.error(`存储数据失败, 失败代码:${e.code}, 失败原因:${e.message}`);
            }
          })
        Button('3.备份数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            try{
              let file = 'bk001';
              this.kvStore.backup(file, (err) => {
                if(err) {
                  console.error(`备份数据失败, 失败代码:${err.code}, 失败原因:${err.message}`);
                } else {
                  Prompt.showToast({
                    message: `备份数据成功,备份文件:${file}`,
                    duration: 1500
                  })
                }
              })
            } catch (e) {
              console.error(`备份数据失败, 失败代码:${e.code}, 失败原因:${e.message}`);
            }
          })
        Button('4.删除数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {

            try{
              this.kvStore.delete('name',(err) => {
                if(err) {
                  console.error(`删除数据失败, 失败代码:${err.code}, 失败原因:${err.message}`);
                  return;
                }
                Prompt.showToast({
                  message: '删除数据成功!',
                  duration: 1500
                })
                this.message = ''; // 删除数据成功后把message置空
              })
            } catch (e) {
              console.error(`删除数据失败, 失败代码:${e.code}, 失败原因:${e.message}`);
            }
          })
        Button('5.恢复数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            this.message = ''
            try{
              this.kvStore.restore('bk001',(err) => {
                if(err) {
                  console.error(`删除数据失败, 失败代码:${err.code}, 失败原因:${err.message}`);
                } else {
                  try{
                    this.kvStore.get('name',(err,data) => {
                      if(err) {
                        console.error(`获取数据失败, 失败代码:${err.code}, 失败原因:${err.message}`);
                        return;
                      }
                      this.message = data.toString(); // 恢复数据后把message的值赋为恢复后的值
                    })
                  } catch (e) {
                    console.error(`获取数据失败, 失败代码:${err.code}, 失败原因:${err.message}`);
                  }
                }
              })
            } catch (e) {
              console.error(`恢复数据失败, 失败代码:${e.code}, 失败原因:${e.message}`);
            }
          })
        Button('6、删除备份')
          .width('50%')
          .onClick(() => {
            let files = ['bk001'];
            try{
              this.kvStore.deleteBatch(files).then((data) => {
                Prompt.showToast({
                  message: `删除备份成功,文件:${data[0]},结果:${data[1]}`,
                })
              }).catch((err) => {
                console.error(`删除备份失败, 失败代码:${err.code}, 失败原因:${err.message}`);
              })
            } catch (e) {
              console.error(`删除备份失败, 失败代码:${e.code}, 失败原因:${e.message}`);
            }
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

6.5.2、关系型数据库备份与恢复

关系型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复。

import relationalStore from '@ohos.data.relationalStore';
import Prompt from '@system.prompt';
const SQL_CREATE_TABLE: string = 'create table if not exists employee(id integer primary key autoincrement, name text not null, age integer, salary real, codes blob)';

@Entry
@Component
struct RdbStroeBack {
  @State message: string = '';
  rdbStore: relationalStore.RdbStore = globalThis.rdbStore;

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .margin({bottom:10})
        Button('1.创建数据表')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            try{
              this.rdbStore.executeSql(SQL_CREATE_TABLE);
              Prompt.showToast({
                message:'创建数据表成功',
                duration:1500
              })
            } catch (e){
              console.error(`创建数据表失败!Code:${e.code},Message:${e.message}`);
            }
          })
        Button('2.向数据表插入数据')
          .margin({bottom:10})
          .width('50%')
          .onClick(() => {
            let valueBucket = {
              'name': '赵子龙',
              'age': 20,
              'salary': 100.5,
              'codes': new Uint8Array([1,2,3,4,5])
            };
            this.rdbStore.insert('employee',valueBucket).then((rowId) => {
              Prompt.showToast({
                message: '插入数据成功!',
                duration: 1500
              })
              this.message = `${rowId} - ${valueBucket.name} - ${valueBucket.salary}`
            }).catch((err) => {
              console.error(`插入数据失败!Code:${err.code},Message:${err.message}`);
            })
          })
        Button('3.备份数据库')
          .margin({bottom:10})
          .width('50%')
          .onClick(() => {
            this.rdbStore.backup('dbBackup.db',(err) => {
              if(err) {
                console.error(`备份数据库失败!Code:${err.code},Message:${err.message}`);
                return;
              }
              Prompt.showToast({
                message: '备份数据库成功!',
                duration: 1500
              })
            })
          })
        Button('4.删除数据')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            let predicates = new relationalStore.RdbPredicates('employee');
            predicates.equalTo('name','赵子龙');
            this.rdbStore.delete(predicates).then((rows) => {
              if(rows > 0) {
                this.message = '';  // 删除后清空界面展示信息
              }
            }).catch((e) => {
              console.error(`删除指定数据失败!Code:${e.code},Message:${e.message}`);
            })
          })
        Button('5.恢复数据')
          .width('50%')
          .onClick(() => {
            this.rdbStore.restore('dbBackup.db',(err) => {
              if(err) {
                console.error(`恢复数据失败. Code:${err.code},message:${err.message}`);
              } else {
                let predicates = new relationalStore.RdbPredicates('employee');
                predicates.equalTo('name','赵子龙');
                this.rdbStore.query(predicates,['id','name','salary'],(err,resultSet) => {
                  if(err) {
                    console.error(`查询数据失败. Code:${err.code},message:${err.message}`);
                  } else {
                    if(resultSet.rowCount > 0) {
                      resultSet.goToFirstRow();
                      this.message = resultSet.getLong(resultSet.getColumnIndex('id')) + ' - '
                      + resultSet.getString(resultSet.getColumnIndex('name')) + ' - '
                      + resultSet.getDouble(resultSet.getColumnIndex('salary'));
                    }
                  }
                })
              }

            })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

6.6、数据库加密

6.6.1、使用场景

为了增强数据库的安全性,数据库提供了一个安全适用的数据库加密能力,从而对数据库存储的内容实施有效保护。通过数据库加密等安全方法实现了数据库数据存储的保密性和完整性要求,使得数据库以密文方式存储并在密态方式下工作,确保了数据安全。

注意:加密后的数据库只能通过接口进行访问,无法通过其它的方式来打开数据库文件。数据库加密的属性在创建数据库时确认,无法变更。

键值型数据库和关系型数据库都直接数据加密操作

6.6.2、键值型数据库加密

通过options中encrypt参数来控制是否加密,默认是false,表示不加密,当这个参数为true时则表示加密处理。

import distributedKVStore from '@ohos.data.distributedKVStore'
import Prompt from '@system.prompt';

const options = {
  createIfMissing: true,
  encrypt: true, // 数据库加密
  backup: false,
  kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
  securityLevel: distributedKVStore.SecurityLevel.S2
};

@Entry
@Component
struct KVStoreEncrypt {
  @State message: string = ''
  kvManager:distributedKVStore.KVManager = globalThis.kvManager;
  kvStore: distributedKVStore.SingleKVStore;

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .margin({bottom:10})
        Button('创建加密数据库')
          .width('50%')
          .margin({bottom:10})
          .onClick(() => {
            this.kvManager.getKVStore('ms1',options,(err,store:distributedKVStore.SingleKVStore) => {
              if(err) {
                console.error(`创建加密数据库失败!,代码:${err.code},原因:${err.message}`);
                return;
              }
              this.kvStore = store;
              Prompt.showToast({
                message: `创建加密数据库成功!`,
                duration: 2000
              })
            })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

其中kvManager对象还是在EntryAbility.ts的onCreate或onWindowStageCreate中创建的

    // KVManagerConfig
    let context = this.context;
    const KvManagerConfig = {
      context: context,
      bundleName: 'com.xiaoxie'
    };
    try{
      let kvManager = distributedKVStore.createKVManager(KvManagerConfig);
      console.info('Succeeded in create KVManager.')
      globalThis.kvManager = kvManager;
    } catch (err){
      console.error(`Failed to create KVManager, Code:${err.code},Message:${err.Message}`);
    }

6.6.3、关系型数据库加密

关系型数据库,过在创建relationalStore.RdbStore时给定的config参数中指定encrypt:true即可。

这个参数默认是false的

    // RdbStore
    const STORE_CONFIG = {
      name: 'RdbTest.db', // 数据库名称
      securityLevel: relationalStore.SecurityLevel.S1 // 数据库的安全级别
    };

    relationalStore.getRdbStore(this.context, STORE_CONFIG, (err,store) => {
      if(err) {
        console.error(`Failed to create rdbstore. Code:${err.code},Message:${err.message}`);
        return;
      }
      console.info('Succeeded in create RdbStore.')
      globalThis.rdbStore = store;
    })

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1296483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gitlab注册无中国区电话验证问题

众所周知gitlab对中国区不友好&#xff0c;无法直接注册&#xff0c;页面无法选择86的手机号进行验证码发送。 Google上众多的方案是修改dom&#xff0c;而且时间大约是21年以前。 修改dom&#xff0c;对于现在的VUE、React框架来说是没有用的&#xff0c;所以不用尝试。 直接看…

springboot3远程调用

RPC 两个服务器之间的调用 远程请求 内部服务之间的调用 可以通过 cloud 注册中心 openfeign等 外部服务的调用 http请求 外部协议 api:远程接口 sdk&#xff1a;本地调用 调用阿里云的天气请求

Navicat 技术指引 | 适用于 GaussDB 分布式的日志查询与配置设置

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

Python---random库

目录 基本随机数函数(): rand.seed() random() 扩展随机数函数(): random库包含两类函数&#xff1a;基本随机数函数&#xff0c;扩展随机数函数 基本随机数函数:seed(),random() 扩展随机数函数&#xff1a;randint,getrandbits(),uniform(),randrange(),choice(),shuff…

分布式和微服务区别

1.分布式 微服务和分布式的区别 1.将一个大的系统划分为多个业务模块&#xff0c;业务模块分别部署到不同的机器上&#xff0c;各个业务模块之间通过接口进行数据交互。区别分布式的方式是根据不同机器不同业务。 2.分布式是否属于微服务&#xff1f; 答案是肯定的。微服务的意…

微信小程序引入Vant Weapp修改样式不起作用,使用外部样式类进行覆盖

一、引入Vant Weapp后样式问题 在项目中使用第三方组件修改css样式时,总是出现各种各样问题,修改的css样式不起作用,没有效果,效果不符合预期等。 栗子(引入一个搜索框组件)实现效果: 左侧有一个搜索文字背景为蓝色,接着跟一个搜索框 wxml <view class"container&q…

cache 2.单机并发缓存

0.对原教程的一些见解 个人认为原教程中两点知识的引入不够友好。 首先是只读数据结构 ByteView 的引入使用是有点迷茫的&#xff0c;可能不能很好理解为什么需要ByteView。 第二是主体结构 Group的引入也疑惑。其实要是熟悉groupcache&#xff0c;那对结构Group的使用是清晰…

修改pip源

修改pip源 永久修改 PS C:\Users\Dell> pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/Writing to C:\Users\Dell\AppData\Roaming\pip\pip.ini临时修改 pip install -i(即--index-url简写) http://mirrors.aliyun.com/pypi/simple/ selenium…

图像叠加中文字体

目录 1) 前言2) freetype下载3) Demo3.1) 下载3.2) 编译3.3) 运行3.4) 结果3.5) 更详细的使用见目录中说明 4) 积少成多 1) 前言 最近在做图片、视频叠加文字&#xff0c;要求支持中文&#xff0c;基本原理是将图片或视频解码后叠加文字&#xff0c;之后做图片或视频编码即可。…

一文讲解关于MCU启动原理的几个关键问题

MCU最开始一启动后去哪里读代码&#xff1f; CPU上电启动后被设计为去地址0x00000000位置处读取代码&#xff1b;首先会连续读取两个字&#xff0c;分别是栈指针初始值和复位异常处理函数的地址&#xff1b;然后跳去执行复位异常处理函数。 当然在一些早期的ARM处理器设计中&a…

【计算机网络学习之路】HTTP请求

目录 前言 HTTP请求报文格式 一. 请求行 HTTP请求方法 GET和POST的区别 URL 二. 请求头 常见的Header 常见的额请求体数据类型 三. 请求体 结束语 前言 HTTP是应用层的一个协议。实际我们访问一个网页&#xff0c;都会像该网页的服务器发送HTTP请求&#xff0c;服务…

nodejs+vue+微信小程序+python+PHP的黄山旅游景点购票系统设计与实现-计算机毕业设计推荐

本文首先对该系统进行了详细地描述&#xff0c;然后对该系统进行了详细的描述。管理人员增加了系统首页、个人中心、用户管理、景点分类管理、景点简介管理、旅游路线管理、文章分类管理、公告文章管理、系统管理理等功能。黄山旅游景点购票系统是根据当前的现实需要&#xff0…

ELK(五)—集群搭建

写目录 ip规划ElasticSearch集群集群节点搭建集群es切片和副本切片&#xff08;Shard&#xff09;&#xff1a;副本&#xff08;Replica&#xff09;&#xff1a; 故障转移postman创建索引的情况直接在面板中创建索引总结 ip规划 ip名称服务192.168.150.190elk_masterelastics…

web漏洞原理与防御策略,web漏洞怎么挖掘

目录 Web安全的重要性 ​编辑常见的Web漏洞类型及其原理&#xff1a; 1、跨站脚本攻击&#xff08;XSS&#xff09;: 2、SQL注入: 3、跨站请求伪造&#xff08;CSRF&#xff09;: 4、远程文件包含&#xff08;RFI&#xff09;和本地文件包含&#xff08;LFI&#xff09;:…

深入浅出:HTTPS单向与双向认证及证书解析20231208

介绍: 网络安全的核心之一是了解和实施HTTPS认证。本文将探讨HTTPS单向认证和双向认证的区别&#xff0c;以及SSL证书和CA证书在这些过程中的作用&#xff0c;并通过Nginx配置实例具体说明。 第一部分&#xff1a;HTTPS单向认证 定义及工作原理&#xff1a;HTTPS单向认证是一…

数据分析基础之《matplotlib(6)—饼图》

一、饼图介绍 1、什么是饼图 饼图广泛的应用在各个领域&#xff0c;用于表示不同分类的占比情况&#xff0c;通过弧度大小来对比各种分类。饼图通过将一个圆饼按照分类的占比划分成多个区块&#xff0c;整个圆饼代表数据的总量&#xff0c;每个区块&#xff08;圆弧&#xff0…

Python网络爬虫的基础理解-对应的自我理解误区

##通过一个中国大学大学排名爬虫的示例进行基础性理解 以软科中国最好大学排名为分析对象&#xff0c;基于requests库和bs4库编写爬虫程序&#xff0c;对2015年至2019年间的中国大学排名数据进行爬取&#xff1a;&#xff08;1&#xff09;按照排名先后顺序输出不同年份的前10…

生产上线需要注意的安全漏洞

一、关闭swagger 1、关闭swagger v3 # 需同时设置auto-startupfalse&#xff0c;否则/v3/api-docs等接口仍能继续访问 springfox:documentation:enabled: falseauto-startup: falseswagger-ui:enabled: false 2、关闭swagger v2 # 只要不是true就不启用 swagger:enable: fa…

力扣题:数字与字符串间转换-12.9

力扣题-12.9 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;412. Fizz Buzz 解题思想&#xff1a;直接遍历添加至answer即可 class Solution(object):def fizzBuzz(self, n):""":type n: int:rtype: List[str]"""…

【银行测试】金融项目+测试方法范围分析,功能/接口/性能/安全...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、金融行业软件特…