HarmonyOS 数据持久化开发实战:KVStore、关系型数据库与 Preferences## 一、前言数据持久化是移动应用开发中的核心需求。HarmonyOS 为开发者提供了多种数据存储方案,从轻量级的 Preferences 到高性能的 KVStore,再到功能完备的关系型数据库(RelationalStore),满足不同场景的需求。本文将以 HarmonyOS 5.0.0(API 12)为基础,通过完整可运行的代码示例,详细讲解三种主流数据持久化方案的使用方法和最佳实践。## 二、数据持久化方案概览| 方案 | 适用场景 | 数据容量 | 查询能力 | 性能 ||------|---------|---------|---------|------|| Preferences | 少量配置数据 | 小 | 无(键值对) | 一般 || KVStore | 键值对数据 | 大 | 支持谓词查询 | 高 || RelationalStore | 结构化数据 | 大 | SQL 查询 | 高 |## 三、Preferences:轻量级首选项Preferences 适合存储少量配置信息,如用户设置、应用偏好等。### 3.1 基本用法import { preferences } from '@kit.ArkData';
@Entry
@Component
struct PreferencesDemo {
@State username: string = '';
@State fontSize: number = 16;
@State isDarkMode: boolean = false;
private prefs?: preferences.Preferences;
async aboutToAppear() {
const options: preferences.Options = { name: 'app_settings' };
this.prefs = await preferences.getPreferences(getContext(), options.name);
await this.loadSettings();
}
async loadSettings() {
if (!this.prefs) return;
try {
this.username = await this.prefs.get('username', '') as string;
this.fontSize = await this.prefs.get('fontSize', 16) as number;
this.isDarkMode = await this.prefs.get('isDarkMode', false) as boolean;
} catch (error) {
console.error(加载设置失败: ${error});
}
}
async saveUsername(name: string) {
if (!this.prefs) return;
try {
await this.prefs.put('username', name);
await this.prefs.flush(); // 持久化到磁盘
this.username = name;
} catch (error) {
console.error(保存用户名失败: ${error});
}
}
async toggleDarkMode() {
if (!this.prefs) return;
try {
const newMode = !this.isDarkMode;
await this.prefs.put('isDarkMode', newMode);
await this.prefs.flush();
this.isDarkMode = newMode;
} catch (error) {
console.error(切换主题失败: ${error});
}
}
build() {
Column({ space: 20 }) {
Text('Preferences 示例')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Row({ space: 8 }) {
TextInput({ placeholder: '输入用户名', text: this.username })
.layoutWeight(1).height(48)
.onChange((value: string) => { this.saveUsername(value) })
}.width('100%')
Text(`当前字体大小: ${this.fontSize}`).fontSize(this.fontSize)
Row({ space: 8 }) {
Button('A-').onClick(() => {
this.fontSize = Math.max(12, this.fontSize - 2);
this.prefs?.put('fontSize', this.fontSize);
this.prefs?.flush();
})
Button('A+').onClick(() => {
this.fontSize = Math.min(32, this.fontSize + 2);
this.prefs?.put('fontSize', this.fontSize);
this.prefs?.flush();
})
}
Row({ space: 8 }) {
Text(`深色模式: ${this.isDarkMode ? '开启' : '关闭'}`).fontSize(16)
Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
.onChange((isOn: boolean) => { this.toggleDarkMode() })
}
Button('清除所有设置')
.backgroundColor('#FF4444').fontSize(16)
.onClick(async () => {
if (this.prefs) {
await this.prefs.clear();
await this.prefs.flush();
this.username = '';
this.fontSize = 16;
this.isDarkMode = false;
}
})
}
.width('100%').padding(20)
.backgroundColor(this.isDarkMode ? '#1A1A2E' : '#FFFFFF')
.height('100%')
}
}
3.2 Preferences 使用须知- 数据量限制 :单个 Preferences 文件不建议超过 1MB- 并发写入 :不支持多线程并发写入- flush() :修改后需要调用 flush() 持久化到磁盘- 多实例:可以创建多个 Preferences 实例区分不同模块## 四、KVStore:高性能键值存储KVStore 是 HarmonyOS 提供的高性能键值存储方案,支持谓词查询。### 4.1 创建和使用 KVStoreimport { distributedKVStore } from '@kit.ArkData';
@Entry
@Component
struct KVStoreDemo {
@State storeStatus: string = '未连接';
@State keyCount: number = 0;
@State searchText: string = '';
@State searchResults: string\[\] = \[\];
private kvStore?: distributedKVStore.SingleKVStore;
private storeId: string = 'my_kv_store';
async aboutToAppear() {
try {
const context = getContext(this);
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
this.kvStore = await distributedKVStore.getKVStore<distributedKVStore.SingleKVStore>(
context, this.storeId, options
);
this.storeStatus = 'KVStore 已连接';
await this.refreshKeyCount();
} catch (error) {
this.storeStatus = `连接失败: ${error}`;
}
}
async refreshKeyCount() {
if (!this.kvStore) return;
const entries = await this.kvStore.getEntries('');
this.keyCount = entries.length;
}
async addData(key: string, value: string) {
if (!this.kvStore || !key.trim()) return;
try {
await this.kvStore.put(key.trim(), value.trim());
await this.refreshKeyCount();
} catch (error) {
console.error(添加数据失败: ${error});
}
}
async searchData(prefix: string) {
if (!this.kvStore) return;
try {
const entries = await this.kvStore.getEntries(prefix);
this.searchResults = entries.map(e => ${e.key} = ${e.value.value});
} catch (error) {
this.searchResults = `查询失败: ${error}`;
}
}
async deleteData(key: string) {
if (!this.kvStore) return;
try {
await this.kvStore.delete(key);
await this.refreshKeyCount();
} catch (error) {
console.error(删除失败: ${error});
}
}
build() {
Column({ space: 16 }) {
Text('KVStore 示例').fontSize(24).fontWeight(FontWeight.Bold)
Text(this.storeStatus).fontSize(14)
.fontColor(this.storeStatus.includes('失败') ? '#FF4444' : '#4CAF50')
Text(当前数据量: ${this.keyCount} 条).fontSize(16).fontColor('#666')
Button('添加测试数据').fontSize(16).backgroundColor('#007AFF').width('100%')
.onClick(() => { this.addData(`user_${this.keyCount + 1}`, `value_${Date.now()}`) })
Divider()
Row({ space: 8 }) {
TextInput({ placeholder: '搜索 Key 前缀' }).layoutWeight(1).height(40)
.onChange((value: string) => { this.searchText = value })
}.width('100%')
Button('搜索').fontSize(16).width('100%')
.onClick(() => { this.searchData(this.searchText) })
if (this.searchResults.length > 0) {
List() {
ForEach(this.searchResults, (item: string) => {
ListItem() {
Text(item).fontSize(14).width('100%').padding(8)
.backgroundColor('#F5F5F5').borderRadius(4)
}
})
}.layoutWeight(1)
}
}.width('100%').height('100%').padding(20)
}
}
五、RelationalStore:关系型数据库RelationalStore 支持完整的 SQL 查询,适合结构化数据。### 5.1 数据库管理与 CRUD 操作import { relationalStore } from '@kit.ArkData';
@Component
struct DatabaseManager {
private rdbStore?: relationalStore.RdbStore;
async initDatabase() {
const context = getContext(this);
const storeConfig: relationalStore.StoreConfig = {
name: 'company.db', securityLevel: relationalStore.SecurityLevel.S1
};
this.rdbStore = await relationalStore.getRdbStore(context, storeConfig);
await this.rdbStore.executeSql(CREATE TABLE IF NOT EXISTS employee ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, department TEXT, salary REAL, hire_date TEXT ));
console.info('数据库初始化成功');
}
async insertEmployee(name: string, age: number, department: string, salary: number): Promise {
if (!this.rdbStore) return -1;
const valueBucket: relationalStore.ValuesBucket = {
'name': name, 'age': age, 'department': department,
'salary': salary, 'hire_date': new Date().toISOString().split('T')0
};
const rowId = await this.rdbStore.insert('employee', valueBucket);
console.info(插入成功, rowId: ${rowId});
return rowId;
}
async queryAllEmployees(): Promise<relationalStore.ValuesBucket\[\]> {
if (!this.rdbStore) return \[\];
const predicates = new relationalStore.RdbPredicates('employee');
const resultSet = await this.rdbStore.query(predicates);
const results: relationalStore.ValuesBucket\[\] = \[\];
while (resultSet.goToNextRow()) {
results.push({
'id': resultSet.getLong(resultSet.getColumnIndex('id')),
'name': resultSet.getString(resultSet.getColumnIndex('name')),
'age': resultSet.getLong(resultSet.getColumnIndex('age')),
'department': resultSet.getString(resultSet.getColumnIndex('department')),
'salary': resultSet.getDouble(resultSet.getColumnIndex('salary')),
'hire_date': resultSet.getString(resultSet.getColumnIndex('hire_date'))
});
}
resultSet.close();
return results;
}
async queryByDepartment(department: string): Promise<relationalStore.ValuesBucket\[\]> {
if (!this.rdbStore) return \[\];
const predicates = new relationalStore.RdbPredicates('employee');
predicates.equalTo('department', department);
const resultSet = await this.rdbStore.query(predicates);
const results: relationalStore.ValuesBucket\[\] = \[\];
while (resultSet.goToNextRow()) {
results.push({
'name': resultSet.getString(resultSet.getColumnIndex('name')),
'salary': resultSet.getDouble(resultSet.getColumnIndex('salary'))
});
}
resultSet.close();
return results;
}
async updateSalary(employeeId: number, newSalary: number) {
if (!this.rdbStore) return;
const valueBucket: relationalStore.ValuesBucket = { 'salary': newSalary };
const predicates = new relationalStore.RdbPredicates('employee');
predicates.equalTo('id', employeeId);
const rows = await this.rdbStore.update(valueBucket, predicates);
console.info(更新了 ${rows} 行);
}
async deleteEmployee(employeeId: number) {
if (!this.rdbStore) return;
const predicates = new relationalStore.RdbPredicates('employee');
predicates.equalTo('id', employeeId);
const rows = await this.rdbStore.delete(predicates);
console.info(删除了 ${rows} 行);
}
}
5.2 员工管理界面@Entry
@Component
struct EmployeePage {
@State employees: relationalStore.ValuesBucket\[\] = \[\];
@State status: string = '初始化中...';
private db: DatabaseManager = new DatabaseManager();
async aboutToAppear() {
await this.db.initDatabase();
this.status = '数据库就绪';
await this.db.insertEmployee('张三', 28, '技术部', 15000);
await this.db.insertEmployee('李四', 32, '市场部', 12000);
await this.db.insertEmployee('王五', 25, '技术部', 18000);
await this.db.insertEmployee('赵六', 30, '人事部', 10000);
await this.loadEmployees();
}
async loadEmployees() {
this.employees = await this.db.queryAllEmployees();
this.status = 共 ${this.employees.length} 名员工;
}
build() {
Column({ space: 12 }) {
Text('员工管理').fontSize(24).fontWeight(FontWeight.Bold)
Text(this.status).fontSize(14).fontColor('#888')
List({ space: 4 }) {
ForEach(this.employees, (emp: relationalStore.ValuesBucket) => {
ListItem() {
Row({ space: 12 }) {
Column({ space: 4 }) {
Text(emp['name'] as string).fontSize(16).fontWeight(FontWeight.Medium)
Text(`${emp['department']} · ${emp['age']}岁`).fontSize(12).fontColor('#888')
}.layoutWeight(1)
Column({ space: 2 }) {
Text(`¥${emp['salary']}`).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#007AFF')
Text(emp['hire_date'] as string).fontSize(11).fontColor('#AAA')
}.alignItems(HorizontalAlign.End)
}.width('100%').padding(12).backgroundColor('#FAFAFA').borderRadius(8)
}
}, (emp: relationalStore.ValuesBucket) => emp['id'].toString())
}.layoutWeight(1)
Row({ space: 8 }) {
Button('添加员工').fontSize(14).backgroundColor('#007AFF').layoutWeight(1)
.onClick(async () => {
await this.db.insertEmployee('新员工', Math.floor(Math.random()*20)+20, '技术部', Math.floor(Math.random()*20000)+8000);
await this.loadEmployees();
})
Button('刷新').fontSize(14).backgroundColor('#4CAF50').layoutWeight(1)
.onClick(async () => { await this.loadEmployees() })
}.width('100%').padding({ bottom: 12 })
}.width('100%').height('100%').padding(16)
}
}
六、实战案例:笔记应用import { relationalStore } from '@kit.ArkData';
interface Note {
id: number; title: string; content: string; category: string;
createdAt: string; updatedAt: string;
}
@Component
struct NoteManager {
private rdbStore?: relationalStore.RdbStore;
async initDB() {
const storeConfig: relationalStore.StoreConfig = { name: 'notes.db', securityLevel: relationalStore.SecurityLevel.S1 };
this.rdbStore = await relationalStore.getRdbStore(getContext(this), storeConfig);
await this.rdbStore.executeSql(CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT DEFAULT '', category TEXT DEFAULT '未分类', created_at TEXT NOT NULL, updated_at TEXT NOT NULL ));
}
async createNote(title: string, content: string): Promise {
if (!this.rdbStore || !title.trim()) return -1;
const now = new Date().toISOString();
const vb: relationalStore.ValuesBucket = { 'title': title.trim(), 'content': content.trim(), 'category': '未分类', 'created_at': now, 'updated_at': now };
return await this.rdbStore.insert('notes', vb);
}
async queryRecentNotes(limit: number = 20): Promise<Note\[\]> {
if (!this.rdbStore) return \[\];
const resultSet = await this.rdbStore.querySql('SELECT id, title, content, category, created_at, updated_at FROM notes ORDER BY updated_at DESC LIMIT ?', limit.toString());
const notes: Note\[\] = \[\];
while (resultSet.goToNextRow()) {
notes.push({ id: resultSet.getLong(0), title: resultSet.getString(1), content: resultSet.getString(2), category: resultSet.getString(3), createdAt: resultSet.getString(4), updatedAt: resultSet.getString(5) });
}
resultSet.close();
return notes;
}
async updateNote(id: number, title: string, content: string) {
if (!this.rdbStore) return;
const vb: relationalStore.ValuesBucket = { 'title': title, 'content': content, 'updated_at': new Date().toISOString() };
const predicates = new relationalStore.RdbPredicates('notes');
predicates.equalTo('id', id);
await this.rdbStore.update(vb, predicates);
}
async deleteNote(id: number) {
if (!this.rdbStore) return;
const predicates = new relationalStore.RdbPredicates('notes');
predicates.equalTo('id', id);
await this.rdbStore.delete(predicates);
}
async searchNotes(keyword: string): Promise<Note\[\]> {
if (!this.rdbStore) return \[\];
const predicates = new relationalStore.RdbPredicates('notes');
predicates.like('title', %${keyword}%);
predicates.or().like('content', %${keyword}%);
const resultSet = await this.rdbStore.query(predicates);
const notes: Note\[\] = \[\];
while (resultSet.goToNextRow()) {
notes.push({ id: resultSet.getLong(0), title: resultSet.getString(1), content: resultSet.getString(2), category: resultSet.getString(3), createdAt: resultSet.getString(4), updatedAt: resultSet.getString(5) });
}
resultSet.close();
return notes;
}
}
@Entry
@Component
struct NoteApp {
@State notes: Note\[\] = \[\];
@State showEditor: boolean = false;
@State editingNote: Note | null = null;
@State searchKeyword: string = '';
@State titleInput: string = '';
@State contentInput: string = '';
private manager: NoteManager = new NoteManager();
async aboutToAppear() {
await this.manager.initDB();
await this.loadNotes();
}
async loadNotes() { this.notes = await this.manager.queryRecentNotes(50) }
build() {
Stack() {
Column({ space: 8 }) {
Row() {
Text('📝 我的笔记').fontSize(24).fontWeight(FontWeight.Bold)
Blank()
Button('+ 新建').fontSize(14).backgroundColor('#007AFF')
.onClick(() => { this.editingNote = null; this.titleInput = ''; this.contentInput = ''; this.showEditor = true })
}.width('100%').padding({ top: 12, bottom: 4 })
Row({ space: 8 }) {
TextInput({ placeholder: '搜索笔记...' }).layoutWeight(1).height(40)
.onChange((value: string) => { this.searchKeyword = value })
Button('搜索').fontSize(14)
.onClick(async () => {
if (this.searchKeyword.trim()) this.notes = await this.manager.searchNotes(this.searchKeyword);
else await this.loadNotes();
})
}.width('100%')
Text(`共 ${this.notes.length} 篇笔记`).fontSize(12).fontColor('#AAA')
if (this.notes.length === 0) {
Column({ space: 12 }) {
Text('📭').fontSize(48)
Text('还没有笔记').fontSize(16).fontColor('#888')
}.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
} else {
List({ space: 8 }) {
ForEach(this.notes, (note: Note) => {
ListItem() {
Row({ space: 12 }) {
Column({ space: 4 }) {
Text(note.title).fontSize(16).fontWeight(FontWeight.Medium).maxLines(1)
Text(note.content).fontSize(13).fontColor('#888').maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
}.layoutWeight(1)
Column({ space: 4 }) {
Text(note.updatedAt.split('T')[0]).fontSize(10).fontColor('#AAA')
Button('删除').fontSize(10).fontColor('#FF4444').backgroundColor('transparent')
.onClick(async () => { await this.manager.deleteNote(note.id); await this.loadNotes() })
}.alignItems(HorizontalAlign.End)
}
.width('100%').padding(12).backgroundColor('#FAFAFA').borderRadius(8)
.onClick(() => { this.editingNote = note; this.titleInput = note.title; this.contentInput = note.content; this.showEditor = true })
}
}, (note: Note) => note.id.toString())
}.layoutWeight(1)
}
}
.padding(16).width('100%').height('100%').backgroundColor('#FFFFFF')
if (this.showEditor) {
Column() {
Column({ space: 12 }) {
Row() {
Text(this.editingNote ? '编辑笔记' : '新建笔记').fontSize(20).fontWeight(FontWeight.Bold)
Blank()
Button('取消').fontSize(14).backgroundColor('#EEEEEE').fontColor('#333')
.onClick(() => { this.showEditor = false })
Button('保存').fontSize(14).backgroundColor('#007AFF').margin({ left: 8 })
.onClick(async () => {
if (this.editingNote) await this.manager.updateNote(this.editingNote.id, this.titleInput, this.contentInput);
else await this.manager.createNote(this.titleInput, this.contentInput);
await this.loadNotes();
this.showEditor = false;
})
}
TextInput({ placeholder: '标题', text: this.editingNote?.title || '' }).fontSize(20).height(48).width('100%')
.onChange((value: string) => { this.titleInput = value })
TextArea({ placeholder: '输入笔记内容...', text: this.editingNote?.content || '' }).fontSize(16).layoutWeight(1).width('100%')
.onChange((value: string) => { this.contentInput = value })
}
.width('90%').height('85%').padding(20).backgroundColor('#FFFFFF').borderRadius(16)
.shadow({ radius: 20, color: '#20000000' })
}
.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor('#40000000')
}
}
}
}