应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。HarmonyOS 标准系统支持典型存储数据形态包括如下三类:
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)
}
}
键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格、员工工号及今日是否已出勤等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。
约束限制
键值型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有 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 | 表示数据库的安全级别为关键级别,业界法律法规中定义的特殊数据类型,涉及个人的最私密领域的信息或者一旦泄露、篡改、破坏、销毁可能会给个人或组织造成重大的不利影响数据。例如:政治观点、宗教和哲学信仰、工会成员资格、基因数据、生物信息、健康和性生活状况、性取向等或设备认证鉴权、个人的信用卡等财务信息 |