HarmonyOS开发(九):数据管理

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、导入用户首选项模块

TypeScript 复制代码
import dataPreferences from '@ohos.data.preferences';

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

TypeScript 复制代码
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存在对应的持久化文件,则同时删除该持久化文件,包括指定文件及其备份文件、损坏文件。

TypeScript 复制代码
// 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

  }
}
TypeScript 复制代码
// 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()中创建。

TypeScript 复制代码
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()方法删除指定键值的数据

TypeScript 复制代码
// 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 分钟前
鸿蒙之路的坑
华为·harmonyos
轻口味2 小时前
【每日学点鸿蒙知识】沙箱目录、图片压缩、characteristicsArray、gm-crypto 国密加解密、通知权限
pytorch·华为·harmonyos
xo198820115 小时前
鸿蒙人脸识别
redis·华为·harmonyos
塞尔维亚大汉6 小时前
【OpenHarmony】 鸿蒙 UI开发之CircleIndicator
harmonyos·arkui
BisonLiu6 小时前
华为仓颉鸿蒙HarmonyOS NEXT仓颉原生数据网络HTTP请求(ohos.net.http)
harmonyos
BisonLiu6 小时前
华为仓颉鸿蒙NEXT原生加解密算法库框架
harmonyos
变色龙云6 小时前
网页生成鸿蒙App
华为·harmonyos
BisonLiu6 小时前
华为仓颉鸿蒙HarmonyOS NEXT仓颉原生ohos.request(上传下载)
harmonyos
s_daqing6 小时前
华为手机鸿蒙4.2连接不上adb
华为·智能手机·harmonyos
Lucky me.6 小时前
鸿蒙开发使用axios请求后端网络服务出现该错误
华为·harmonyos