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%')
  }
}