ArkData(方舟数据管理)之 应用数据持久化

应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。HarmonyOS 标准系统支持典型存储数据形态包括如下三类:

  • ① 用户首选项(Preferences):通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
  • ② 键值型数据库(KV-Store):一种非关系型数据库,其数据以“键值”对的形式进行组织、索引和存储,其中“键”作为唯一标识符。适合很少数据关系和业务关系的业务数据存储,尤其是分布式应用场景中。
  • ③ 关系型数据库(RelationalStore):一种关系型数据库,以行和列的形式存储数据,广泛用于应用中的关系型数据的处理,包括一系列的增、删、改、查等接口,开发者也可以运行自定义的 SQL 语句来满足复杂业务场景的需要。

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

Preferences:用户首选项为应用提供 Key-Value 型的轻量级数据持久化能力,并对其修改和查询。Preferences 会将数据缓存在内存中,当用户读取的时候,能够快速从内存中获取数据,当需要持久化时可以使用 flush 接口将内存中的数据写入持久化文件中。

适用的场景一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等

注意:Preferences 实例中会一次性加载全部数据,随着存放的数据量越多而导致应用占用的内存越大,因此,不适合存放过多的数据!

用户首选项持久化数据都是 Key-Value 对;Key 非空且唯一,长度不能超过 80 个字节;Value 允许为空,如果是字符串类型,必须采用 UTF-8 编码,长度不能超过 8192 个字节。

方法 作用
getPreferencesSync(context, options) 获取 Preferences 实例
putSync(key, value) 将数据写入 Preferences 实例(内存中)
getSync(key, defValue) 获取键对应的值,如果值为 null,返回默认数据 defValue
hasSync(key) 检查 Preferences 实例是否包含名为给定 Key 的存储键值对
deleteSync(key) 从 Preferences 实例中删除名为给定 Key 的存储键值对
clearSync 清除缓存的 Preferences 实例中的所有数据
flush(callback) 将当前 Preferences 实例的数据存储到用户首选项文件中
on(‘change’, callback) 订阅的数据发生变更后,在执行 flush 方法后,触发 callback 回调
off(‘change’, callback) 取消数据变更订阅
deletePreferences(context, options, callback) 从内存中移除指定的 Preferences 实例。若 Preferences 实例有对应的持久化文件,则同时删除其持久化文件
使用

注意:一定要保存到 ROM 哦

import { preferences } from '@kit.ArkData'

@Entry
@Component
struct Index {
  p?: preferences.Preferences

  build() {
    Column({ space: 10 }) {
      Text('使用Prefences操作首选项数据')
        .fontSize(25)

      Button('保存到ROM').onClick(_=>{
        this.p?.flushSync() //注意!如果不冲刷,修改只是在内存中RAM进行!没有写出到存储ROM
        console.log("保存成功!")
      })

      Button('1.打开Preferences实例(本质是读取XML文件数据到内存)').onClick(_ => {
        let ctx = getContext() //通过上下文读取沙箱目录
        this.p = preferences.getPreferencesSync(ctx, {
          name: 'MyStore01'   //本质就是底层XML文件名
        })
        console.log('--首选项对象MyStore01打开完成', JSON.stringify(this.p))
      })

      Button('2.从Preferences实例中读取数据').onClick(_ => {
        if ( Object.keys(this.p?.getAllSync()).length !== 0) {
          // let ut = this.p.getSync('userTheme', 'dark') // 获取其中一个键值对
          console.log('--首选项所有数据:',JSON.stringify(this.p?.getAllSync()))
        } else {
          console.log('--首选项数据不存在')
        }
      })

      Button('3.向Preferences实例中写出数据').onClick(_ => {
        this.p?.putSync('userTheme', 'light-' + Date.now())
        this.p?.putSync('fontSize', 20)
        this.p?.putSync('lineSpace', true)
        console.log('--首选项数据userTheme fontSize lineSpace写出完成')
      })

      Button('4.在Preferences实例中修改数据 userTheme').onClick(_ => {
        this.p?.putSync('userTheme', 'light-super')
        console.log('--首选项数据userTheme修改完成')
      })

      Button('5.从Preferences实例中删除数据').onClick(_ => {
        this.p?.deleteSync('userTheme')
        // this.p?.clearSync()    //清空/删除所有的KV对
        console.log('--首选项数据userTheme删除完成')
      })

      Button('6.删除Preferences实例').onClick(async _ => {
        await preferences.deletePreferences(getContext(), {
          name: 'MyStore01'
        })
        console.log('--首选项实例对象MyStore01删除成功')
      })
    }
    .height('100%')
    .width('100%')
    .padding(10)
  }
}

通过键值型数据库实现数据持久化

键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格、员工工号及今日是否已出勤等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。

约束限制

  • 设备协同数据库,针对每条记录,Key 的长度 ≤896 Byte,Value 的长度<4 MB。
  • 单版本数据库,针对每条记录,Key 的长度 ≤1 KB,Value 的长度<4 MB。
  • 每个应用程序最多支持同时打开 16 个键值型分布式数据库。
  • 键值型数据库事件回调方法中不允许进行阻塞操作,例如修改 UI 组件。

键值型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有 callback 和 Promise 两种返回形式:

方法 描述
createKVManager(config) 创建一个 KVManager 对象实例,用于管理数据库对象
getKVStore(storeId, options) 指定 options 和 storeId,创建并得到指定类型的 KVStore 数据库
put(key, value) 添加指定类型的键值对到数据库
get(key) 获取指定键的值
delete(key) 从数据库中删除指定键值的数据
使用
import { distributedKVStore } from '@kit.ArkData'

let KVStore: distributedKVStore.SingleKVStore | undefined = undefined //可在单一设备上使用的键值对存储对象

@Entry
@Component
struct Index {
  kvStore = KVStore as distributedKVStore.SingleKVStore

  build() {
    Column({ space: 10 }) {
      Text('使用KVStore操作海量的键值对数据')
        .fontSize(30)

      Button('1.打开键值对数据存储对象').onClick(async _ => {
        //创建键值对数据库管理器
        let kvMgr = distributedKVStore.createKVManager({
          bundleName: 'com.example.quanguokefei',
          context: getContext()
        })
        //使用管理器创建键值对存储对象
        this.kvStore = await kvMgr.getKVStore('MyKVStore', {
          securityLevel: distributedKVStore.SecurityLevel.S1  //SQLite中的数据是加密存储的
        })
        console.log('--键值对存储对象创建完成')
      })

      Button('2-1.读取一条键值对数据').onClick(async _ => {
        try {
          let v = await this.kvStore.get('a6')
          console.log('--一条键值对数据: a6:', v)
        } catch (err) {
          console.log('--读取数据错误:', JSON.stringify(err))
          //15100004: Not Found,表示未查找到指定的键值对数据
        }
      })

      Button('2-2.读取多条键值对数据').onClick(async _ => {
        try {
          let list = await this.kvStore.getEntries('ct')
          console.log('--键名以 ct 为前缀的数据:', JSON.stringify(list))
        } catch (err) {
          console.log('--读取数据错误:', JSON.stringify(err))
          //getEntries()方法如果没有查询到数据,会返回[]
        }
      })

      Button('3.添加键值对数据').onClick(_ => {
        this.kvStore.put('c260', 1000000)
        this.kvStore.put('e300', 1000001)
        this.kvStore.put('ct-5', 2000000)
        this.kvStore.put('ct-6', 2000002)
        this.kvStore.put('a4', 3000000)
        this.kvStore.put('a6', 3000003)
        console.log('--键值对数据保存完成')
      })

      Button('4.修改一条键值对数据').onClick(_ => {
        this.kvStore.put('e300', 6000000) //put()时指定的key不存在就是添加,否则就是修改
        console.log('--键值对数据修改完成')
      })

      Button('5-1.删除一条键值对数据').onClick(_ => {
        this.kvStore.delete('c260')
        console.log('--一条键值对数据删除成功')
      })

      Button('5-2.删除多条键值对数据').onClick(_ => {
        let list = ['ct-4', 'ct-6']
        this.kvStore.deleteBatch(list) //删除一批指定的键
        console.log('--多条键值对数据删除成功')
      })
    }
    .height('100%')
    .width('100%')
    .padding(10)
  }
}

通过关系型数据库实现数据持久化

关系型数据库基于 SQLite 组件,适用于存储包含复杂关系数据的场景,比如我的所有好友的信息,需要包括姓名、电话、住址等,又或者公司中与我有过联系的所有雇员信息,需要包括姓名、工号、职位等,适用于数据之间有较强的对应关系,复杂程度比键值型数据更高的情形

基本概念

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

  • 结果集:指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便地拿到用户想要的数据。


以下是关系型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有 callback 和 Promise 两种返回形式: Relational Database

方法名 说明
getRdbStore(context, options) 获得一个相关的 RdbStore,操作关系型数据库,用户可以根据自己的需求配置 RdbStore 的参数,然后通过 RdbStore 调用相关接口可以执行相关的数据操作
executeSql(sql, bindArgs) 执行包含指定参数但不返回值的 SQL 语句,例如DDL/DML
insert(table, values) 向目标表中插入一行数据
update(values, predicates) 根据 RdbPredicates 的指定实例对象更新数据库中的数据
delete(predicates) 根据 RdbPredicates 的指定实例对象从数据库中删除数据
query(predicates, columns) 根据指定条件查询数据库中的数据(执行 DQL)
deleteRdbStore(context, name) 删除数据库
使用

创建表时不能使用谓词(RdbPredicates),其他操作建议使用谓词法,不要浪费封装这些函数工程师的汗水。

import { relationalStore } from '@kit.ArkData'
import { JSON } from '@kit.ArkTS'

@Entry
@Component
struct Index {
  rdb?: relationalStore.RdbStore

  build() {
    Column({ space: 10 }) {
      Text('使用RelationStore操作海量的关系型数据')
        .fontSize(30)

      Button('1.打开关系型存储对象').onClick(async _ => {
        //打开一个关系型数据库存储对象
        this.rdb = await relationalStore.getRdbStore(getContext(), {
          name: 'MyStore03',
          securityLevel: relationalStore.SecurityLevel.S1
        })
        //提示:SQLite没有提供查询当前存在哪些表/表中有哪些列相关操作 —— 开发者必须自己记下来!
        //每当数据库中创建了一个表或者修改了一个表中的列(即修改了数据库的结构了),就分配一个唯一的“数据版本号”
        if (this.rdb.version == 0) { //数据的初始版本号,此时没有任何表存在
          //创建一个保存好友信息的表
          //一个表中只能有一列声明为“主键列”——该列上不允许出现重复值; 主键列可以声明为“自增长”
          let sql =
            'CREATE TABLE friends(fid INTEGER PRIMARY KEY AUTOINCREMENT, fname TEXT(32), isMarried INTEGER, salary REAL)'
          await this.rdb.executeSql(sql) //executeSql()没有返回值,专用于执行没有返回值的SQL
          console.log('--关系型数据库第1版本执行完成,创建了一个“好友”信息表')
          this.rdb.version = 1
        } else if (this.rdb.version == 1) {
          //创建一个保存当前用户订单信息的表
          this.rdb.version = 2
        } else if (this.rdb.version == 2) {
          //为好友信息表中添加一个新的列
          this.rdb.version = 3
        }
        console.log('--成功得到了关系型数据库仓库实例')
      })

      Button('2-1.插入多行关系型数据——SQL法').onClick(async _ => {
        let sql = 'INSERT INTO friends(fid,fname,isMarried,salary) VALUES(?,?,?,?)'
        await this.rdb?.executeSql(sql, [1, '老刘', 0, 5000])
        await this.rdb?.executeSql(sql, [2, '老张', 1, 6000.06])
        await this.rdb?.executeSql(sql, [3, '老李', 1, 7000])
        await this.rdb?.executeSql(sql, [8, '老王', 1, 8000.08])
        await this.rdb?.executeSql(sql, [10, '老赵', 0, 9000])

        let sql2 = 'INSERT INTO friends(fname,isMarried,salary) VALUES(?,?,?)'
        await this.rdb?.executeSql(sql2, ['小赵', 0, 2000]) //fid:11
        await this.rdb?.executeSql(sql2, ['小秦', 1, 2500]) //fid:12
        await this.rdb?.executeSql(sql2, ['小高', 1, 2800]) //fid:13
        console.log('--好友表中插入了多行')
      })

      Button('2-2.插入多行关系型数据——insert函数法').onClick(async _ => {
        let rowId = await this.rdb?.insert('friends', {
          fid: 20,
          fname: 'Tom',
          isMarried: 0,
          salary: 4000
        })
        console.log('--好友表中插入一行数据,行编号为:', rowId) //行编号:就是该行数据的主键列上的值

        let rowId2 = await this.rdb?.insert('friends', { fname: 'Mary', isMarried: 1, salary: 4500 })
        console.log('--好友表中插入一行数据,行编号为:', rowId2) //行编号:就是该行数据的主键列上的值
      })

      Button('3-1.查询一行关系型数据——SQL语句法').onClick(async _ => {
        let sql = 'SELECT fid,fname,salary,isMarried FROM  friends  WHERE fid=?' //WHERE指定筛选条件
        let rs = await this.rdb?.querySql(sql, [2])

        if (rs?.goToNextRow()) {
          console.log('--查询到一行数据:', rs.getLong(0), rs.getString(1), rs.getDouble(2), rs.getLong(3))
        } else {
          console.log('--指定编号的好友没有查到')
        }
      })

      Button('3-1.查询一行关系型数据——谓词法').onClick(async _ => {
        let p = new relationalStore.RdbPredicates('friends') // FROM friends
        p.equalTo('fid', 2) // WHERE fid=2
        let rs = await this.rdb?.query(p, ['fid', 'fname', 'salary', 'isMarried']) // SELECT fid,fname,salary,isMarried

        if (rs?.goToNextRow()) {
          console.log('--查询到一行数据:', rs.getLong(0), rs.getString(1), rs.getDouble(2), rs.getLong(3))
        } else {
          console.log('--指定编号的好友没有查到')
        }
      })

      Button('3-2.查询多行关系型数据——SQL语句法').onClick(async _ => {
        let sql = 'SELECT  fid,fname,isMarried,salary  FROM  friends'
        let rs = await this.rdb?.querySql(sql) //返回值是一个ResetSet(结果集)对象

        while (rs?.goToNextRow()) {
          console.log('--读取到了一行数据:', rs?.getLong(0), rs?.getString(1), rs?.getLong(2), rs?.getDouble(3))
        }

        console.log('--查询完成')
      })

      Button('3-2.查询多行关系型数据——谓词法').onClick(async _ => {
        let p = new relationalStore.RdbPredicates('friends') //谓词对象
        let rs = await this.rdb?.query(p, ['fid', 'fname', 'isMarried', 'salary'])

        while (rs?.goToNextRow()) {
          console.log('--读取到了一行数据:', rs?.getLong(0), rs?.getString(1), rs?.getLong(2), rs?.getDouble(3))
        }

        console.log('--查询完成')
      })

      Button('4-1.修改一行关系型数据——SQL法').onClick(async _ => {
        let sql = 'UPDATE friends SET isMarried=?,salary=?   WHERE  fid=?'
        try {
          await this.rdb?.executeSql(sql, [1, 8000, 20])
          console.log('--修改操作执行完成,此操作修改了多少行数据呢?', '不知道')
        } catch (err) {
          console.log('--20号好友修改失败', JSON.stringify(err))
        }
      })
      Button('4-2.修改一行关系型数据——update函数法').onClick(async _ => {
        try {
          let p = new relationalStore.RdbPredicates('friends')
          p.equalTo('fid', 20)
          let count = await this.rdb?.update({ isMarried: 0, salary: 8888 }, p)
          console.log('--修改操作执行完成,此操作修改了多少行数据呢?', count)
        } catch (err) {
          console.log('--20号好友修改失败', JSON.stringify(err))
        }
      })

      Button('5-1.删除一行关系型数据——SQL法').onClick(async _ => {
        let sql = 'DELETE  FROM   friends  WHERE  fid=?'
        await this.rdb?.executeSql(sql, [21])
        console.log('--删除操作执行完成!影响了多少行数据呢?', '不知道')
      })

      Button('5-2.删除一行关系型数据——delete函数法').onClick(async _ => {
        let p = new relationalStore.RdbPredicates('friends')
        p.equalTo('fid', 20)
        let count = await this.rdb?.delete(p)
        console.log('--删除操作执行完成!影响了多少行数据呢?', count)
      })

      Button('6.删除关系型数据库本身').onClick(async _ => {
        await relationalStore.deleteRdbStore(getContext(), 'MyStore03')
        console.log('--数据表连同整个数据库本身已经删除')
      })
    }
    .height('100%')
    .width('100%')
    .padding(10)
  }
}

数据库的安全级别枚举

安全级别 说明
S1 表示数据库的安全级别为低级别,数据的泄露、篡改、破坏、销毁可能会给个人或组织导致有限的不利影响。例如:性别、国籍,用户申请记录等
S2 表示数据库的安全级别为中级别,数据的泄露、篡改、破坏、销毁可能会给个人或组织导致严重的不利影响。>例如:个人详细通信地址,姓名昵称等
S3 表示数据库的安全级别为高级别,数据的泄露、篡改、破坏、销毁可能会给个人或组织导致严峻的不利影响。例如:个人实时精确定位信息、运动轨迹等
S4 表示数据库的安全级别为关键级别,业界法律法规中定义的特殊数据类型,涉及个人的最私密领域的信息或者一旦泄露、篡改、破坏、销毁可能会给个人或组织造成重大的不利影响数据。例如:政治观点、宗教和哲学信仰、工会成员资格、基因数据、生物信息、健康和性生活状况、性取向等或设备认证鉴权、个人的信用卡等财务信息

你可能感兴趣的:(HarmonyOS,harmonyos)