RelationalStore数据库实战:鸿蒙日记数据持久化方案 #跟着淼哥学鸿蒙
本文介绍了HarmonyOS的RelationalStore关系型数据库的使用方法。RelationalStore基于SQLite实现,具有SQL支持、事务管理、高效查询等优势,适合处理复杂数据。文章通过日记应用案例,详细演示了数据库配置初始化(包括创建表、索引)、数据模型定义以及CRUD操作实现(单条/批量插入)。相比Preferences和文件存储方案,RelationalStore在结构化数
·
📝 文章概述
数据持久化是任何应用的核心功能之一。HarmonyOS提供的RelationalStore是一个功能强大的关系型数据库系统,基于SQLite实现。本文将通过日记应用的实际案例,深入讲解RelationalStore的使用方法、最佳实践和性能优化技巧。
🎯 为什么选择RelationalStore?
技术优势
mindmap
root((RelationalStore))
功能特性
SQL支持
事务管理
索引优化
数据加密
性能优势
高效查询
批量操作
内存优化
异步API
易用性
简洁API
Promise支持
类型安全
错误处理
安全性
数据隔离
权限控制
安全等级
数据备份
与其他方案对比
| 特性 | RelationalStore | Preferences | 文件存储 |
|---|---|---|---|
| 数据结构 | 关系型表格 | 键值对 | 自定义 |
| 查询能力 | ⭐⭐⭐⭐⭐ SQL | ⭐⭐ Key查询 | ⭐ 文件遍历 |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 数据量 | 大量数据 | 少量配置 | 任意 |
| 事务支持 | ✅ | ❌ | ❌ |
| 适用场景 | 复杂数据、查询需求 | 简单配置 | 文件、媒体 |
🚀 快速开始
第一步:数据库配置与初始化
import { relationalStore } from '@kit.ArkData'
// 数据库管理类
export class Rdb {
rdbStore: relationalStore.RdbStore | null = null
private sqlCreateTable: string = ''
// 🔥 数据库配置
private STORE_CONFIG: relationalStore.StoreConfig = {
name: 'diary.db', // 数据库文件名
securityLevel: 1, // 安全等级:1=最高
encrypt: false, // 是否加密
customDir: '' // 自定义目录(可选)
}
constructor(sqlCreateTable: string) {
this.sqlCreateTable = sqlCreateTable
}
// 🔥 获取或创建数据库实例
async getRdbStore() {
// 如果已存在,直接返回
if (this.rdbStore != null) {
return this.rdbStore.executeSql(this.sqlCreateTable);
}
try {
// 获取应用上下文
const context = getContext();
// 创建或打开数据库
this.rdbStore = await relationalStore.getRdbStore(
context,
this.STORE_CONFIG
);
console.info('✅ 数据库创建/打开成功');
// 创建表
await this.rdbStore.executeSql(this.sqlCreateTable);
console.info('✅ 数据表创建成功');
} catch (error) {
console.error('❌ 数据库初始化失败:', error);
throw error;
}
}
// 🔥 关闭数据库
async closeStore() {
if (this.rdbStore) {
await this.rdbStore.close();
this.rdbStore = null;
console.info('✅ 数据库已关闭');
}
}
// 🔥 删除数据库
async deleteStore() {
const context = getContext();
await relationalStore.deleteRdbStore(context, this.STORE_CONFIG.name);
this.rdbStore = null;
console.info('✅ 数据库已删除');
}
}
第二步:定义数据模型
// 日记数据模型
export class DiaryRecord {
id?: number
title: string
content: string
time: string
constructor(title: string, content: string, time: string) {
this.title = title
this.content = content
this.time = time
}
}
// 日记记录集合类
export class DiaryRecordSet {
private records: DiaryRecord[] = []
// 添加记录
addRecord(record: DiaryRecord) {
this.records.push(record)
}
// 转换为数组
convertToArray(): DiaryRecord[] {
return this.records
}
// 获取数量
get length(): number {
return this.records.length
}
}
第三步:创建数据表
// SQL建表语句
const SQL_CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS diary (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
time TEXT NOT NULL
)
`;
// 创建索引(优化查询性能)
const SQL_CREATE_INDEX = `
CREATE INDEX IF NOT EXISTS idx_time ON diary(time DESC)
`;
// 初始化数据库
const rdb = new Rdb(SQL_CREATE_TABLE);
await rdb.getRdbStore();
// 创建索引
if (rdb.rdbStore) {
await rdb.rdbStore.executeSql(SQL_CREATE_INDEX);
console.info('✅ 索引创建成功');
}
💡 CRUD操作实战
1. 插入数据(Create)
class DiaryAPI {
private rdb: Rdb
constructor() {
this.rdb = new Rdb(SQL_CREATE_TABLE);
}
// 🔥 插入单条记录
async insertRecord(diary: DiaryRecord): Promise<number> {
try {
// 确保数据库已初始化
await this.rdb.getRdbStore();
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
// 构造ValuesBucket对象
const valueBucket: relationalStore.ValuesBucket = {
title: diary.title,
content: diary.content,
time: diary.time
};
// 插入数据
const rowId = await this.rdb.rdbStore.insert('diary', valueBucket);
console.info(`✅ 日记插入成功,ID: ${rowId}`);
return rowId;
} catch (error) {
console.error('❌ 插入日记失败:', error);
throw error;
}
}
// 🔥 批量插入
async insertBatch(diaries: DiaryRecord[]): Promise<number> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
let successCount = 0;
// 使用事务批量插入
await this.rdb.rdbStore.executeSql('BEGIN TRANSACTION');
for (const diary of diaries) {
const valueBucket: relationalStore.ValuesBucket = {
title: diary.title,
content: diary.content,
time: diary.time
};
await this.rdb.rdbStore.insert('diary', valueBucket);
successCount++;
}
await this.rdb.rdbStore.executeSql('COMMIT');
console.info(`✅ 批量插入成功,数量: ${successCount}`);
return successCount;
} catch (error) {
// 回滚事务
if (this.rdb.rdbStore) {
await this.rdb.rdbStore.executeSql('ROLLBACK');
}
console.error('❌ 批量插入失败:', error);
throw error;
}
}
}
2. 查询数据(Read)
// 🔥 查询所有记录
async queryAllRecords(): Promise<DiaryRecordSet> {
await this.rdb.getRdbStore();
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
// 查询条件(查询所有,按时间倒序)
const predicates = new relationalStore.RdbPredicates('diary');
predicates.orderByDesc('time'); // 按时间降序排列
// 执行查询
const resultSet = await this.rdb.rdbStore.query(predicates);
console.info(`✅ 查询成功,找到${resultSet.rowCount}条记录`);
// 转换结果
const recordSet = new DiaryRecordSet();
// 遍历结果集
if (resultSet.goToFirstRow()) {
do {
const diary = new DiaryRecord('', '', '');
diary.id = resultSet.getLong(resultSet.getColumnIndex('id'));
diary.title = resultSet.getString(resultSet.getColumnIndex('title'));
diary.content = resultSet.getString(resultSet.getColumnIndex('content'));
diary.time = resultSet.getString(resultSet.getColumnIndex('time'));
recordSet.addRecord(diary);
} while (resultSet.goToNextRow());
}
// 关闭结果集
resultSet.close();
return recordSet;
} catch (error) {
console.error('❌ 查询失败:', error);
throw error;
}
}
// 🔥 根据ID查询
async queryById(id: number): Promise<DiaryRecord | null> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const predicates = new relationalStore.RdbPredicates('diary');
predicates.equalTo('id', id); // WHERE id = ?
const resultSet = await this.rdb.rdbStore.query(predicates);
let diary: DiaryRecord | null = null;
if (resultSet.goToFirstRow()) {
diary = new DiaryRecord('', '', '');
diary.id = resultSet.getLong(resultSet.getColumnIndex('id'));
diary.title = resultSet.getString(resultSet.getColumnIndex('title'));
diary.content = resultSet.getString(resultSet.getColumnIndex('content'));
diary.time = resultSet.getString(resultSet.getColumnIndex('time'));
}
resultSet.close();
return diary;
} catch (error) {
console.error('❌ 根据ID查询失败:', error);
throw error;
}
}
// 🔥 模糊搜索
async searchByKeyword(keyword: string): Promise<DiaryRecordSet> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const predicates = new relationalStore.RdbPredicates('diary');
// WHERE title LIKE '%keyword%' OR content LIKE '%keyword%'
predicates.like('title', `%${keyword}%`)
.or()
.like('content', `%${keyword}%`);
predicates.orderByDesc('time');
const resultSet = await this.rdb.rdbStore.query(predicates);
const recordSet = new DiaryRecordSet();
if (resultSet.goToFirstRow()) {
do {
const diary = new DiaryRecord('', '', '');
diary.id = resultSet.getLong(resultSet.getColumnIndex('id'));
diary.title = resultSet.getString(resultSet.getColumnIndex('title'));
diary.content = resultSet.getString(resultSet.getColumnIndex('content'));
diary.time = resultSet.getString(resultSet.getColumnIndex('time'));
recordSet.addRecord(diary);
} while (resultSet.goToNextRow());
}
resultSet.close();
console.info(`✅ 搜索完成,找到${recordSet.length}条匹配记录`);
return recordSet;
} catch (error) {
console.error('❌ 搜索失败:', error);
throw error;
}
}
// 🔥 分页查询
async queryByPage(pageNum: number, pageSize: number): Promise<DiaryRecordSet> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const predicates = new relationalStore.RdbPredicates('diary');
predicates.orderByDesc('time');
// 限制返回结果数量和偏移量
predicates.limit(pageSize, (pageNum - 1) * pageSize);
const resultSet = await this.rdb.rdbStore.query(predicates);
const recordSet = new DiaryRecordSet();
if (resultSet.goToFirstRow()) {
do {
const diary = new DiaryRecord('', '', '');
diary.id = resultSet.getLong(resultSet.getColumnIndex('id'));
diary.title = resultSet.getString(resultSet.getColumnIndex('title'));
diary.content = resultSet.getString(resultSet.getColumnIndex('content'));
diary.time = resultSet.getString(resultSet.getColumnIndex('time'));
recordSet.addRecord(diary);
} while (resultSet.goToNextRow());
}
resultSet.close();
console.info(`✅ 分页查询完成,第${pageNum}页,${recordSet.length}条记录`);
return recordSet;
} catch (error) {
console.error('❌ 分页查询失败:', error);
throw error;
}
}
3. 更新数据(Update)
// 🔥 更新记录
async updateRecord(diary: DiaryRecord): Promise<number> {
if (!this.rdb.rdbStore || !diary.id) {
throw new Error('数据库未初始化或记录ID无效');
}
try {
// 构造更新数据
const valueBucket: relationalStore.ValuesBucket = {
title: diary.title,
content: diary.content,
time: diary.time
};
// 更新条件
const predicates = new relationalStore.RdbPredicates('diary');
predicates.equalTo('id', diary.id);
// 执行更新
const rows = await this.rdb.rdbStore.update(valueBucket, predicates);
console.info(`✅ 更新成功,影响${rows}行`);
return rows;
} catch (error) {
console.error('❌ 更新失败:', error);
throw error;
}
}
// 🔥 批量更新
async updateBatch(updates: Map<number, DiaryRecord>): Promise<number> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
let totalRows = 0;
await this.rdb.rdbStore.executeSql('BEGIN TRANSACTION');
for (const [id, diary] of updates) {
const valueBucket: relationalStore.ValuesBucket = {
title: diary.title,
content: diary.content,
time: diary.time
};
const predicates = new relationalStore.RdbPredicates('diary');
predicates.equalTo('id', id);
const rows = await this.rdb.rdbStore.update(valueBucket, predicates);
totalRows += rows;
}
await this.rdb.rdbStore.executeSql('COMMIT');
console.info(`✅ 批量更新成功,影响${totalRows}行`);
return totalRows;
} catch (error) {
if (this.rdb.rdbStore) {
await this.rdb.rdbStore.executeSql('ROLLBACK');
}
console.error('❌ 批量更新失败:', error);
throw error;
}
}
4. 删除数据(Delete)
// 🔥 删除单条记录
async deleteRecord(id: number): Promise<number> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const predicates = new relationalStore.RdbPredicates('diary');
predicates.equalTo('id', id);
const rows = await this.rdb.rdbStore.delete(predicates);
console.info(`✅ 删除成功,影响${rows}行`);
return rows;
} catch (error) {
console.error('❌ 删除失败:', error);
throw error;
}
}
// 🔥 删除所有记录
async deleteAll(): Promise<number> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const predicates = new relationalStore.RdbPredicates('diary');
const rows = await this.rdb.rdbStore.delete(predicates);
console.info(`✅ 删除所有记录成功,共${rows}条`);
return rows;
} catch (error) {
console.error('❌ 删除所有记录失败:', error);
throw error;
}
}
// 🔥 根据条件删除
async deleteByCondition(startDate: string, endDate: string): Promise<number> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const predicates = new relationalStore.RdbPredicates('diary');
predicates.between('time', startDate, endDate);
const rows = await this.rdb.rdbStore.delete(predicates);
console.info(`✅ 条件删除成功,影响${rows}行`);
return rows;
} catch (error) {
console.error('❌ 条件删除失败:', error);
throw error;
}
}
📊 高级查询技巧
RdbPredicates谓词详解
复杂查询示例
// 🔥 复杂条件查询
async complexQuery(): Promise<DiaryRecordSet> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const predicates = new relationalStore.RdbPredicates('diary');
// (title LIKE '%工作%' OR content LIKE '%工作%')
// AND time >= '2024-01-01'
// ORDER BY time DESC
// LIMIT 10
predicates.beginWrap()
.like('title', '%工作%')
.or()
.like('content', '%工作%')
.endWrap()
.and()
.greaterThanOrEqualTo('time', '2024-01-01')
.orderByDesc('time')
.limit(10);
const resultSet = await this.rdb.rdbStore.query(predicates);
// ... 处理结果集 ...
return recordSet;
} catch (error) {
console.error('❌ 复杂查询失败:', error);
throw error;
}
}
// 🔥 统计查询
async getStatistics(): Promise<{total: number, today: number}> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
// 查询总数
const totalSql = 'SELECT COUNT(*) as total FROM diary';
const totalResult = await this.rdb.rdbStore.querySql(totalSql);
let total = 0;
if (totalResult.goToFirstRow()) {
total = totalResult.getLong(0);
}
totalResult.close();
// 查询今天的日记数
const today = new Date().toISOString().split('T')[0];
const todaySql = `SELECT COUNT(*) as count FROM diary WHERE time LIKE '${today}%'`;
const todayResult = await this.rdb.rdbStore.querySql(todaySql);
let todayCount = 0;
if (todayResult.goToFirstRow()) {
todayCount = todayResult.getLong(0);
}
todayResult.close();
return { total, today: todayCount };
} catch (error) {
console.error('❌ 统计查询失败:', error);
throw error;
}
}
// 🔥 分组查询
async groupByDate(): Promise<Map<string, number>> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
// 按日期分组统计
const sql = `
SELECT
DATE(time) as date,
COUNT(*) as count
FROM diary
GROUP BY DATE(time)
ORDER BY date DESC
`;
const resultSet = await this.rdb.rdbStore.querySql(sql);
const result = new Map<string, number>();
if (resultSet.goToFirstRow()) {
do {
const date = resultSet.getString(0);
const count = resultSet.getLong(1);
result.set(date, count);
} while (resultSet.goToNextRow());
}
resultSet.close();
console.info(`✅ 分组查询完成,${result.size}个日期`);
return result;
} catch (error) {
console.error('❌ 分组查询失败:', error);
throw error;
}
}
🔧 性能优化技巧
1. 创建索引
// 创建索引提升查询性能
async createIndexes() {
if (!this.rdb.rdbStore) {
return;
}
try {
// 时间索引(用于排序和范围查询)
await this.rdb.rdbStore.executeSql(
'CREATE INDEX IF NOT EXISTS idx_time ON diary(time DESC)'
);
// 标题索引(用于搜索)
await this.rdb.rdbStore.executeSql(
'CREATE INDEX IF NOT EXISTS idx_title ON diary(title)'
);
// 全文搜索索引(SQLite FTS5)
await this.rdb.rdbStore.executeSql(`
CREATE VIRTUAL TABLE IF NOT EXISTS diary_fts USING fts5(
title,
content,
content=diary,
content_rowid=id
)
`);
console.info('✅ 索引创建成功');
} catch (error) {
console.error('❌ 创建索引失败:', error);
}
}
// 使用全文搜索索引
async fullTextSearch(keyword: string): Promise<DiaryRecordSet> {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
const sql = `
SELECT diary.*
FROM diary
JOIN diary_fts ON diary.id = diary_fts.rowid
WHERE diary_fts MATCH '${keyword}'
ORDER BY diary.time DESC
`;
const resultSet = await this.rdb.rdbStore.querySql(sql);
// ... 处理结果 ...
return recordSet;
} catch (error) {
console.error('❌ 全文搜索失败:', error);
throw error;
}
}
2. 使用事务
// 🔥 事务示例
async performTransaction() {
if (!this.rdb.rdbStore) {
throw new Error('数据库未初始化');
}
try {
// 开始事务
await this.rdb.rdbStore.executeSql('BEGIN TRANSACTION');
// 执行多个操作
const diary1 = new DiaryRecord('标题1', '内容1', new Date().toISOString());
await this.insertRecord(diary1);
const diary2 = new DiaryRecord('标题2', '内容2', new Date().toISOString());
await this.insertRecord(diary2);
// 提交事务
await this.rdb.rdbStore.executeSql('COMMIT');
console.info('✅ 事务提交成功');
} catch (error) {
// 回滚事务
if (this.rdb.rdbStore) {
await this.rdb.rdbStore.executeSql('ROLLBACK');
console.info('⚠️ 事务已回滚');
}
throw error;
}
}
3. 分页加载
// 🔥 分页加载器
class DiaryPager {
private pageSize: number = 20
private currentPage: number = 0
private hasMore: boolean = true
async loadNextPage(api: DiaryAPI): Promise<DiaryRecord[]> {
if (!this.hasMore) {
return [];
}
this.currentPage++;
const recordSet = await api.queryByPage(this.currentPage, this.pageSize);
const records = recordSet.convertToArray();
// 判断是否还有更多数据
this.hasMore = records.length >= this.pageSize;
console.info(`加载第${this.currentPage}页,${records.length}条记录`);
return records;
}
reset() {
this.currentPage = 0;
this.hasMore = true;
}
}
4. 缓存策略
// 🔥 带缓存的数据访问层
class CachedDiaryAPI extends DiaryAPI {
private cache: Map<number, DiaryRecord> = new Map()
private cacheTimeout: number = 5 * 60 * 1000 // 5分钟
private cacheTimestamps: Map<number, number> = new Map()
async queryById(id: number): Promise<DiaryRecord | null> {
// 检查缓存
const cached = this.cache.get(id);
const timestamp = this.cacheTimestamps.get(id);
if (cached && timestamp && Date.now() - timestamp < this.cacheTimeout) {
console.info(`从缓存获取日记 ID:${id}`);
return cached;
}
// 缓存未命中,查询数据库
const diary = await super.queryById(id);
if (diary) {
// 更新缓存
this.cache.set(id, diary);
this.cacheTimestamps.set(id, Date.now());
}
return diary;
}
async updateRecord(diary: DiaryRecord): Promise<number> {
const rows = await super.updateRecord(diary);
// 更新成功后清除缓存
if (rows > 0 && diary.id) {
this.cache.delete(diary.id);
this.cacheTimestamps.delete(diary.id);
}
return rows;
}
clearCache() {
this.cache.clear();
this.cacheTimestamps.clear();
console.info('缓存已清空');
}
}
🔐 数据安全
1. 数据加密
// 启用数据库加密
private STORE_CONFIG: relationalStore.StoreConfig = {
name: 'diary.db',
securityLevel: 1, // S1级别(最高安全级别)
encrypt: true, // 启用加密
// encryptKey: 'your-key' // 可选:自定义加密密钥
}
2. SQL注入防护
// ❌ 不安全的做法
async unsafeQuery(keyword: string) {
const sql = `SELECT * FROM diary WHERE title = '${keyword}'`;
// 如果keyword包含恶意SQL,会造成SQL注入
}
// ✅ 安全的做法
async safeQuery(keyword: string) {
// 使用参数化查询
const predicates = new relationalStore.RdbPredicates('diary');
predicates.equalTo('title', keyword); // 自动转义
return await this.rdb.rdbStore.query(predicates);
}
3. 数据备份
// 🔥 数据库备份
async backupDatabase(backupPath: string): Promise<boolean> {
try {
const context = getContext();
const dbPath = `${context.databaseDir}/diary.db`;
// 复制数据库文件到备份位置
await fileIo.copyFile(dbPath, backupPath);
console.info(`✅ 数据库备份成功: ${backupPath}`);
return true;
} catch (error) {
console.error('❌ 数据库备份失败:', error);
return false;
}
}
// 🔥 数据库恢复
async restoreDatabase(backupPath: string): Promise<boolean> {
try {
const context = getContext();
const dbPath = `${context.databaseDir}/diary.db`;
// 关闭当前数据库
await this.rdb.closeStore();
// 从备份恢复
await fileIo.copyFile(backupPath, dbPath);
// 重新打开数据库
await this.rdb.getRdbStore();
console.info(`✅ 数据库恢复成功`);
return true;
} catch (error) {
console.error('❌ 数据库恢复失败:', error);
return false;
}
}
📚 最佳实践
✅ 推荐做法
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 初始化 | 应用启动时初始化 | 避免首次查询慢 |
| 查询 | 使用RdbPredicates | 类型安全,防SQL注入 |
| 批量操作 | 使用事务 | 提升性能,保证一致性 |
| 大数据 | 分页加载 | 避免内存溢出 |
| 频繁查询 | 添加索引 | 大幅提升查询速度 |
| 结果集 | 及时关闭 | 释放资源 |
| 敏感数据 | 启用加密 | 保护数据安全 |
❌ 避免做法
- ❌ 不关闭ResultSet(内存泄漏)
- ❌ 主线程执行耗时查询(ANR)
- ❌ 不使用事务的批量操作(性能差)
- ❌ 字符串拼接SQL(SQL注入风险)
- ❌ 忽略错误处理
- ❌ 频繁打开关闭数据库
🎓 总结
RelationalStore为HarmonyOS应用提供了强大的数据持久化能力:
✅ 功能完善:支持SQL查询、事务、索引、加密
✅ 性能优异:异步API、索引优化、批量操作
✅ 易于使用:简洁的API、Promise支持、类型安全
✅ 安全可靠:数据加密、SQL注入防护、事务保证
✅ 生态友好:与鸿蒙生态深度集成
核心要点
- 正确初始化:应用启动时初始化数据库
- 合理建模:设计好表结构和索引
- 安全查询:使用RdbPredicates而非字符串拼接
- 性能优化:事务、索引、分页、缓存
- 资源管理:及时关闭ResultSet和数据库连接
更多推荐



所有评论(0)