HarmonyOS 数据持久化开发实战:KVStore、关系型数据库与 Preferences

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

}

}

七、常见问题与最佳实践| 问题 | 解答 ||------|------|| Preferences vs KVStore? | 数据量少(<100条)用Preferences;需查询用KVStore || 数据库升级? | ALTER TABLE 添加字段,版本号管理迁移脚本 || 性能优化? | 批量插入用事务、创建索引、LIMIT分页、异步操作 |## 八、总结| 方案 | 核心 API | 最佳场景 ||------|----------|---------|| Preferences | preferences.getPreferences() | 用户设置、开关状态 || KVStore | distributedKVStore.getKVStore() | 缓存数据、配置信息 || RelationalStore | relationalStore.getRdbStore() | 结构化数据、复杂查询 |选型建议: 简单配置→Preferences,键值缓存→KVStore,结构化数据+SQL→RelationalStore> 参考文档:华为开发者联盟 HarmonyOS 5.0.0 API 12 --- ArkData 数据管理指南

相关推荐
TrisighT1 小时前
ArkTS 组件传对象还是拆属性?我测了帧率,结果和直觉反着来
harmonyos·arkts
木咺吟1 小时前
鸿蒙原生应用开发实战(四):电影详情与评分评价 — 电影清单App
harmonyos
kisdiem1 小时前
让大模型从“会回答”走向真正调用业务系统
数据库
风华圆舞1 小时前
鸿蒙语音播报功能 的 Flutter 侧封装思路
flutter·华为·harmonyos
IvorySQL1 小时前
PostgreSQL 技术日报 (6月11日)|规划器扩展优化,POSETTE 大会倒计时
数据库·postgresql
胡小禾1 小时前
Redis哨兵模式下主从同步的偏差
数据库·redis·缓存
风华圆舞2 小时前
鸿蒙 + Flutter 下美食探索场景为什么 AI 推荐比传统搜索更自然
flutter·harmonyos·美食
zzqssliu2 小时前
Taocarts接口限流实操:基于Redis实现API防刷与流量管控
数据库·redis·缓存
小博测试成长之路2 小时前
行业日报 | 2026年6月12日:Claude新模型、鸿蒙开发者大会与AI工程化加速
人工智能·harmonyos