应用数据持久化
应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。
HarmonyOS标准系统支持典型的存储数据形态,包括用户首选项、键值型数据库、关系型数据库。
-
用户首选项(Preferences) :通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
-
键值型数据库(KV-Store) :一种非关系型数据库,其数据以“键值”对的形式进行组织、索引和存储,其中“键”作为唯一标识符。适合很少数据关系和业务关系的业务数据存储,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。相比于关系型数据库,更容易做到跨设备跨版本兼容。
-
关系型数据库(RelationalStore) :一种关系型数据库,以行和列的形式存储数据,广泛用于应用中的关系型数据的处理,包括一系列的增、删、改、查等接口,开发者也可以运行自己定义的SQL语句来满足复杂业务场景的需要。
应用数据持久化-用户首选项
概述
用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。当用户希望有一个全局唯一存储的地方,可以采用用户首选项来进行存储。Preferences会将该数据缓存在内存中,当用户读取的时候,能够快速从内存中获取数据,当需要持久化时可以使用flush接口将内存中的数据写入持久化文件中。Preferences会随着存放的数据量越多而导致应用占用的内存越大,因此,Preferences不适合存放过多的数据,也不支持通过配置加密,适用的场景一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等。
约束限制
-
首选项无法保证进程并发安全,会有文件损坏和数据丢失的风险,不支持在多进程场景下使用。
-
Key键为string类型,要求非空且长度不超过1024个字节。
-
如果Value值为string类型,请使用UTF-8编码格式,可以为空,不为空时长度不超过16 * 1024 * 1024个字节。
-
内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。
常用方法
参考华为官方文档:developer.huawei.com/consumer/cn…
注意:记得用真机或者模拟器运行
封装用户首选项工具类
注意:PreferencesUtil文件结尾是ts不是ets
import dataPreferences from '@ohos.data.preferences'
import hilog from '@ohos.hilog';
/**
* 用户首选项(存储简单数据)
*/
export default class PreferencesUtil {
// 用户首选项名称
private static preferenceName: string = 'myStore'
/**
* 创建
* @param context
*/
static createPreferences(context) {
globalThis.getFontPreferences = (() => {
let preferences: Promise<dataPreferences.Preferences> =
dataPreferences.getPreferences(context, this.preferenceName);
return preferences;
});
}
/**
* 存放数据
* @param value
*/
static savePreferencesValue(key: string, value: string) {
globalThis.getFontPreferences().then((preferences) => {
preferences.has(key).then(async (isExist) => {
if (!isExist) {
await preferences.put(key, value);
preferences.flush();
}
}).catch((err) => {
hilog.info(0xFF00, 'preferencesTag', '%{public}s', 'save PreferencesValue fail');
});
}).catch((err) => {
hilog.info(0xFF00, 'preferencesTag', '%{public}s', 'save PreferencesValue fail');
});
}
/**
* 获取数据
* @returns
*/
static async getPreferencesValue(key: string) {
let value: string = '';
const preferences = await globalThis.getFontPreferences();
value = await preferences.get(key, value);
return value;
}
/**
* 删除数据
*/
static async deletePreferencesValue(key: string) {
const preferences: dataPreferences.Preferences = await globalThis.getFontPreferences();
let deleteValue = preferences.delete(key);
deleteValue.then(() => {
hilog.info(0xFF00, 'preferencesTag', '%{public}s', 'delete PreferencesValue success');
}).catch((err) => {
hilog.info(0xFF00, 'preferencesTag', '%{public}s', 'delete PreferencesValue fail');
});
}
}
初始化用户首选项
在EntryAbility文件中初始化用户首选项
onWindowStageCreate(windowStage: window.WindowStage): void {
// 初始化用户首选项
PreferencesUtil.createPreferences(this.context);
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
}
使用用户首选项
注意:用户首选项存的值全局都可以获取到
import PreferencesUtil from '../utils/PreferencesUtil'
@Entry
@Component
struct Index {
build() {
Column() {
Text('存值')
.onClick(() => {
PreferencesUtil.savePreferencesValue('username', '东林')
console.log('preferences save success')
}).margin({bottom:50})
Divider()
Text('点我获取preferences值')
.onClick(async () => {
const username = await PreferencesUtil.getPreferencesValue('username')
console.log('preferences username:' + JSON.stringify(username))
}).margin({bottom:50})
Divider()
Text('删除')
.onClick(async () => {
await PreferencesUtil.deletePreferencesValue('username')
console.log('preferences remove success')
})
}
.height('100%')
.width('100%')
}
}
应用数据持久化-键值型数据库
概述
键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格、员工工号及今日是否已出勤等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。
约束限制
-
设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度<4 MB。
-
单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度<4 MB。
-
每个应用程序最多支持同时打开16个键值型分布式数据库。
-
键值型数据库事件回调方法中不允许进行阻塞操作,例如修改UI组件。
常用方法
1、在生命周期函数中添加代码
import KVStoreUtil from '../utils/KVStoreUtil';
onWindowStageCreate(windowStage: window.WindowStage): void {
// 创建键值型数据库的实例
let context = this.context;
const kvManagerConfig: distributedKVStore.KVManagerConfig = {
context: context,
bundleName: 'com.xt.myApplication'
};
try {
// 创建KVManager实例
let kvManager: distributedKVStore.KVManager | undefined = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
// 创建数据库
let kvStore: distributedKVStore.SingleKVStore | undefined = undefined;
try {
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
// kvStoreType不填时,默认创建多设备协同数据库
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
// 多设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
kvManager.getKVStore<distributedKVStore.SingleKVStore>('storeId', options,
(err, store: distributedKVStore.SingleKVStore) => {
if (err) {
console.error(`Failed to get KVStore: Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
kvStore = store;
// 保存数据库
KVStoreUtil.setKVStore(kvStore)
});
} catch (e) {
let error = e as BusinessError;
console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
}
} catch (e) {
let error = e as BusinessError;
console.error(`Failed to create KVManager. Code:${error.code},message:${error.message}`);
}
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
}
2、创建一个KVStoreUtil工具类
import { distributedKVStore } from '@kit.ArkData';
import { data } from '@kit.TelephonyKit';
export default class KVStoreUtil {
private static kvStore: distributedKVStore.SingleKVStore
static setKVStore(kvStore: distributedKVStore.SingleKVStore) {
KVStoreUtil.kvStore = kvStore;
}
static getKVStore(): distributedKVStore.SingleKVStore {
return KVStoreUtil.kvStore;
}
/**
* 保存数据
* @param key
* @param value
* @returns
*/
static insert(key: string, value: string): Promise<string> {
return new Promise((resolve, reject) => {
KVStoreUtil.kvStore.put(key, value, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
reject(err)
}
console.info('Succeeded in putting data.');
resolve(key + ':' + value)
});
})
}
/**
* 获取
* @param key
* @returns
*/
static get(key: string): Promise<string | number | boolean | Uint8Array | void> {
return new Promise((resolve, reject) => {
KVStoreUtil.kvStore.get(key, (err, data) => {
if (err != undefined) {
console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
reject(err)
}
console.info(`Succeeded in getting data. Data:${data}`);
resolve(key + ':' + data)
});
})
}
/**
* 删除
* @param key
* @returns
*/
static delete(key: string): Promise<string> {
return new Promise((resolve, reject) => {
KVStoreUtil.kvStore.delete(key, (err) => {
if (err !== undefined) {
console.error(`Failed to delete data. Code:${err.code},message:${err.message}`);
reject(err)
}
console.info('Succeeded in deleting data.');
resolve(key)
});
})
}
}
3、在页面中操作数据库
import KVStoreUtil from '../utils/KVStoreUtil'
import { promptAction } from '@kit.ArkUI'
import { BusinessError } from '@ohos.base';
@Entry
@Component
struct Index {
build() {
Column() {
Button('插入数据')
.onClick(() => {
KVStoreUtil.insert('username', '东林').then((data) => {
promptAction.showToast({
message: '保存数据成功' + data
})
}).catch((error: BusinessError) => {
promptAction.showToast({
message: '保存数据失败 ' + error
})
})
}).margin({ bottom: 50 })
Button('查询数据')
.onClick(() => {
KVStoreUtil.get('username').then((data) => {
promptAction.showToast({
message: '查询数据成功:' + data
})
}).catch((error: BusinessError) => {
promptAction.showToast({
message: '查询数据失败 ' + error
})
})
}).margin({ bottom: 50 })
Button('删除数据')
.onClick(() => {
KVStoreUtil.delete('username').then((data) => {
promptAction.showToast({
message: '删除数据成功:' + data
})
}).catch((error: BusinessError) => {
promptAction.showToast({
message: '删除数据失败:' + error
})
})
}).margin({ bottom: 50 })
}
.height('100%')
.width('100%')
}
}
应用数据持久化-关系型数据库
概述
关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。
运行机制
关系型数据库对应用提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。
约束限制
-
系统默认日志方式是WAL(Write Ahead Log)模式,系统默认落盘方式是FULL模式。
-
数据库中有4个读连接和1个写连接,线程获取到空闲读连接时,即可进行读取操作。当没有空闲读连接且有空闲写连接时,会将写连接当做读连接来使用。
-
为保证数据的准确性,数据库同一时间只能支持一个写操作。
-
当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动清除。
-
ArkTS侧支持的基本数据类型:number、string、二进制类型数据、boolean。
-
为保证插入并读取数据成功,建议一条数据不要超过2M。超出该大小,插入成功,读取失败
常用方法
1、在生命周期函数中添加代码
导入模块代码
import { relationalStore } from '@kit.ArkData';
在onWindowStageCreate生命周期函数中添加代码,初始化数据库
// 创建数据库
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'RdbTest.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S3, // 数据库安全级别
encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
customDir: 'customDir/subCustomDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是应用沙箱对应的路径,'/rdb/'表示创建的是关系型数据库,customDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。
};
relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in getting RdbStore.`);
//保存store, 方便后面我们对数据库的操作
RdbUtil.setStore(store)
})
2、创建实体类Student
查询的时候需要用实体类接收下数据库里面数据
export default class Student {
id: number = 0
username: string
age: number = 0
constructor(id: number, username: string, age: number) {
this.id = id
this.username = username
this.age = age
}
}
3、创建工具类RdbUtil
import relationalStore from '@ohos.data.relationalStore';
import Student from '../models/Student';
import { BusinessError } from '@ohos.base';
/**
* 关系型数据库工具类
*/
export default class RdbUtil {
/**
* 数据库对象
*/
private static rdbStore: relationalStore.RdbStore;
static setStore(store: relationalStore.RdbStore) {
RdbUtil.rdbStore = store;
}
static getStore(): relationalStore.RdbStore {
return RdbUtil.rdbStore;
}
/**
* 执行sql
* @param sql
* @returns
*/
static executeSql(sql: string): Promise<void> {
return RdbUtil.getStore().executeSql(sql);
}
/**
* 插入数据
* @param tableName
* @param data
* @returns
*/
static insert(tableName: string, data: relationalStore.ValuesBucket): Promise<number> {
return RdbUtil.getStore().insert(tableName, data);
}
/**
* 查询数据
* @returns
*/
static queryAll(): Promise<Array<Student>> {
let predicates = new relationalStore.RdbPredicates('STUDENT');
return new Promise<Array<Student>>((resolve, reject) => {
RdbUtil.getStore().query(predicates).then((result) => {
let students = new Array<Student>();
while (result.goToNextRow()) {
let student = new Student(
result.getLong(0),
result.getString(1),
result.getLong(2),
);
students.push(student);
}
resolve(students);
}).catch((error: BusinessError) => {
reject(error)
})
})
}
/**
* 删除
* @param id
* @returns
*/
static deleteById(id: number) {
let predicates = new relationalStore.RdbPredicates('STUDENT');
predicates.equalTo('ID', id)
return RdbUtil.getStore().delete(predicates);
}
/**
* 更新
* @param id
* @param data
* @returns
*/
static updateById(id: number, data: relationalStore.ValuesBucket) {
let predicates = new relationalStore.RdbPredicates('STUDENT');
predicates.equalTo('ID', id)
return RdbUtil.getStore().update(data, predicates);
}
}
注意:该工具类里面包含了创建表,新增数据,查询数据,更新数据,删除数据 的api
4、在界面中操作数据库
import RdbUtil from '../utils/RdbUtil';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';
import { relationalStore } from '@kit.ArkData';
import Student from '../models/Student';
@Entry
@Component
struct Index {
build() {
Column() {
Button('创建数据库表')
.onClick(() => {
const SQL_CREATE_TABLE =
'CREATE TABLE IF NOT EXISTS STUDENT (ID INTEGER PRIMARY KEY AUTOINCREMENT, USERNAME TEXT NOT NULL, AGE INTEGER)'; // 建表Sql语句
RdbUtil.executeSql(SQL_CREATE_TABLE)
.then(() => {
promptAction.showToast({
message: 'success create table'
})
}).catch((err: BusinessError) => {
promptAction.showToast({
message: 'fail create table'
})
})
}).margin({ bottom: 50 })
Button('插入数据')
.onClick(() => {
const valueBucket: relationalStore.ValuesBucket = {
'USERNAME': '东林',
'AGE': 18
};
RdbUtil.insert('STUDENT', valueBucket)
.then((updateNumber) => {
promptAction.showToast({
message: 'insert data success ' + updateNumber
})
}).catch((error: BusinessError) => {
promptAction.showToast({
message: 'insert data fail ' + error
})
})
}).margin({ bottom: 50 })
Button('查询数据')
.onClick(() => {
RdbUtil.queryAll()
.then((students: Array<Student>) => {
promptAction.showToast({
message: 'query students success ' + JSON.stringify(students)
})
}).catch((error: BusinessError) => {
promptAction.showToast({
message: ' query students fail ' + error
})
})
}).margin({ bottom: 50 })
Button('修改数据')
.onClick(() => {
const valueBucket: relationalStore.ValuesBucket = {
'USERNAME': '小红',
'AGE': 20
};
RdbUtil.updateById(1, valueBucket)
.then((updateNumber) => {
promptAction.showToast({
message: 'update student success ' + updateNumber.toString()
})
}).catch((err: BusinessError) => {
promptAction.showToast({
message: ' update student fail ' + err
})
})
}).margin({ bottom: 50 })
Button('删除数据')
.onClick(() => {
RdbUtil.deleteById(1)
.then((updateNumber) => {
promptAction.showToast({
message: 'delete student success ' + updateNumber.toString()
})
}).catch((err: BusinessError) => {
promptAction.showToast({
message: 'delete student fail ' + err
})
})
})
}
.height('100%')
.width('100%')
}
}
5、删除数据库
在生命周期函数里面删除数据库
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
// 删除数据库
relationalStore.deleteRdbStore(this.context, 'RdbTest.db', (err) => {
if (err) {
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting RdbStore.');
});
}