【共创季稿事节】HarmonyOS 7.0 数据库实战:基于关系型数据库的离线优先架构设计
文章目录

每日一句正能量
跨过这段路,幸福就在拐角处。
不说“一切都会好起来”这种空话,而是把希望放在一个近在眼前的位置(拐角)。让痛苦有尽头感,人就能再多走几步。
导读
在指导学生开发需要离线功能的鸿蒙应用(如笔记、待办、阅读器)时,数据持久化始终是核心课题。6.x 的
relationalStore已经提供了 SQLite 兼容的关系型数据库能力,但开发者仍需手动编写 SQL、处理版本迁移、实现对象映射,代码冗余且容易出错。HarmonyOS 7.0 极有可能在数据库层引入更完善的 ORM(对象关系映射)支持、自动迁移机制和云端同步管道。本文将基于 6.1 现状与行业 ORM 框架趋势,设计一套完整的"离线优先"架构方案,涵盖数据库设计、ORM 封装、数据迁移和云端同步四大环节。
一、HarmonyOS 6.x 数据库现状:能力可用,工程繁琐
6.1 为开发者提供了 relationalStore 模块,底层基于 SQLite,支持标准的 SQL 操作:
| 能力 | 6.x API | 开发者痛点 |
|---|---|---|
| 数据库创建 | relationalStore.getRdbStore() |
需手动管理数据库版本和 onCreate/onUpgrade |
| 数据操作 | executeSql() / insert() / query() |
需手写 SQL,无类型安全 |
| 对象映射 | 无原生 ORM | 需自行封装 Cursor → Object 转换层 |
| 版本迁移 | RdbOpenCallback.onUpgrade() |
需手动编写 ALTER TABLE 脚本,易出错 |
| 云端同步 | 无内置方案 | 需自行实现增量同步、冲突合并 |
| 事务支持 | beginTransaction() / commit() |
API 层级低,容易遗漏回滚 |
课堂场景中的典型问题:
- 学生修改了数据模型字段后,数据库版本升级脚本写错,导致应用崩溃;
query()返回的ResultSet需要手动按索引取值,字段顺序一改就报错;- 离线编辑的数据如何与云端同步,没有统一策略,各写各的。
7.0 需要从"数据库操作 API"升级为"数据管理框架"。
二、HarmonyOS 7.0 关系型数据库升级推演
2.1 原生 ORM 框架(ArkORM)
7.0 最可能引入的变革是原生 ORM 框架(ArkORM),将数据库操作从"SQL 驱动"转向"对象驱动":
// 7.0 推演:ArkORM 实体定义
import { arkORM } from '@ohos.data.arkORM';
@arkORM.Entity('notes') // 映射到 notes 表
class NoteEntity {
@arkORM.PrimaryKey({ autoIncrement: true })
id: number = 0;
@arkORM.Column('title', { type: 'TEXT', nullable: false })
title: string = '';
@arkORM.Column('content', { type: 'TEXT' })
content: string = '';
@arkORM.Column('create_time', { type: 'INTEGER' })
createTime: number = Date.now();
@arkORM.Column('sync_status', { type: 'INTEGER', default: 0 })
syncStatus: SyncStatus = SyncStatus.PENDING; // 0:待同步 1:已同步 2:冲突
@arkORM.Index(['title', 'create_time']) // 复合索引
indexTitleTime: string = '';
}
2.2 类型安全的查询构建器
// 7.0 推演:类型安全查询
const dao = arkORM.getDAO(NoteEntity);
// 查询所有待同步的笔记
const pendingNotes = await dao.queryBuilder()
.where('syncStatus', '=', SyncStatus.PENDING)
.and('createTime', '>', Date.now() - 86400000)
.orderBy('createTime', 'DESC')
.limit(50)
.getMany();
// 自动生成的 SQL(开发者无需关心):
// SELECT * FROM notes WHERE syncStatus = 0 AND create_time > ? ORDER BY create_time DESC LIMIT 50
2.3 自动迁移(Auto Migration)
6.x 的数据库升级需要开发者手动编写 onUpgrade 逻辑。7.0 的 ArkORM 可能支持自动迁移:
// 7.0 推演:自动迁移配置
@arkORM.Database({
name: 'note_db',
version: 3, // 版本升级时自动检测差异
entities: [NoteEntity, TagEntity, NoteTagEntity],
migrationStrategy: arkORM.MigrationStrategy.AUTO, // 自动迁移
// 或手动指定迁移脚本
migrations: [
{
fromVersion: 1,
toVersion: 2,
script: 'ALTER TABLE notes ADD COLUMN tags TEXT'
},
{
fromVersion: 2,
toVersion: 3,
script: 'CREATE INDEX idx_sync ON notes(sync_status)'
}
]
})
class NoteDatabase {}
自动迁移的工作原理:
- 对比当前实体类定义与数据库实际 Schema;
- 生成差异 DDL(
ALTER TABLE、CREATE INDEX、DROP COLUMN等); - 在事务中按顺序执行迁移脚本;
- 迁移失败时自动回滚,保障数据安全。
三、离线优先架构设计
3.1 架构分层
图1:离线优先数据库架构分层图
图片内容说明(中文):纵向四层结构。最上层"UI层":ArkUI组件,通过ViewModel访问数据。第二层"业务逻辑层":ViewModel、Repository、SyncManager。第三层"数据访问层":ArkORM DAO、本地数据库(SQLite)、云端API客户端。第四层"存储层":本地闪存(加密)、云端数据库(华为云/自建)、分布式数据对象(可选)。各层之间用箭头标注依赖方向,右侧标注"UI层不直接访问数据库"。
3.2 数据模型设计(ER图)
图2:离线优先笔记应用数据库ER图
图片内容说明(中文):三个实体矩形,之间用连线标注关系。①Note(笔记):id(PK)、title、content、create_time、update_time、sync_status、version。②Tag(标签):id(PK)、name、color。③NoteTag(笔记标签关联):note_id(FK)、tag_id(FK)、联合主键。Note与Tag之间通过NoteTag多对多关联。各字段标注数据类型和约束。
3.3 同步状态机
离线优先架构的核心是本地数据始终可写,同步是后台异步过程:
| 状态 | 含义 | 触发条件 |
|---|---|---|
| PENDING | 待同步 | 本地新增/修改后,尚未发起同步 |
| SYNCING | 同步中 | 已向云端发送请求,等待响应 |
| SYNCED | 已同步 | 云端确认接收,本地与云端一致 |
| CONFLICT | 冲突 | 云端数据比本地新,需要人工/自动合并 |
| FAILED | 失败 | 网络异常导致同步失败,需重试 |
四、完整实战代码
4.1 实体定义与 DAO
// model/NoteEntity.ets
import { arkORM } from '@ohos.data.arkORM';
export enum SyncStatus {
PENDING = 0,
SYNCING = 1,
SYNCED = 2,
CONFLICT = 3,
FAILED = 4
}
@arkORM.Entity('notes')
export class NoteEntity {
@arkORM.PrimaryKey({ autoIncrement: true })
id: number = 0;
@arkORM.Column('title', { type: 'TEXT', nullable: false })
title: string = '';
@arkORM.Column('content', { type: 'TEXT' })
content: string = '';
@arkORM.Column('create_time', { type: 'INTEGER' })
createTime: number = Date.now();
@arkORM.Column('update_time', { type: 'INTEGER' })
updateTime: number = Date.now();
@arkORM.Column('sync_status', { type: 'INTEGER', default: 0 })
syncStatus: SyncStatus = SyncStatus.PENDING;
@arkORM.Column('version', { type: 'INTEGER', default: 1 })
version: number = 1; // 乐观锁版本
@arkORM.Column('cloud_id', { type: 'TEXT' })
cloudId: string = ''; // 云端唯一标识
}
// model/TagEntity.ets
@arkORM.Entity('tags')
export class TagEntity {
@arkORM.PrimaryKey({ autoIncrement: true })
id: number = 0;
@arkORM.Column('name', { type: 'TEXT', unique: true })
name: string = '';
@arkORM.Column('color', { type: 'TEXT' })
color: string = '#4ECDC4';
}
// model/NoteTagEntity.ets
@arkORM.Entity('note_tags')
export class NoteTagEntity {
@arkORM.PrimaryKey()
@arkORM.Column('note_id', { type: 'INTEGER' })
noteId: number = 0;
@arkORM.PrimaryKey()
@arkORM.Column('tag_id', { type: 'INTEGER' })
tagId: number = 0;
}
4.2 Repository 层(业务封装)
// repository/NoteRepository.ets
import { arkORM } from '@ohos.data.arkORM';
import { NoteEntity, SyncStatus } from '../model/NoteEntity';
export class NoteRepository {
private dao = arkORM.getDAO(NoteEntity);
// 创建笔记(本地优先)
async createNote(title: string, content: string): Promise<NoteEntity> {
const note = new NoteEntity();
note.title = title;
note.content = content;
note.syncStatus = SyncStatus.PENDING;
note.id = await this.dao.insert(note);
return note;
}
// 更新笔记(乐观锁防止覆盖)
async updateNote(id: number, updates: Partial<NoteEntity>): Promise<boolean> {
const note = await this.dao.findById(id);
if (!note) return false;
// 增加版本号
updates.version = note.version + 1;
updates.updateTime = Date.now();
updates.syncStatus = SyncStatus.PENDING;
const affected = await this.dao.update(id, updates, {
where: { version: note.version } // 乐观锁条件
});
if (affected === 0) {
// 版本冲突,需要重新加载后合并
throw new Error('乐观锁冲突,数据已被其他端修改');
}
return true;
}
// 查询待同步数据
async getPendingNotes(): Promise<NoteEntity[]> {
return await this.dao.queryBuilder()
.where('syncStatus', 'in', [SyncStatus.PENDING, SyncStatus.FAILED])
.orderBy('updateTime', 'ASC')
.getMany();
}
// 标记同步成功
async markSynced(localId: number, cloudId: string): Promise<void> {
await this.dao.update(localId, {
syncStatus: SyncStatus.SYNCED,
cloudId: cloudId
});
}
}
4.3 同步管理器
// sync/SyncManager.ets
import { NoteRepository } from '../repository/NoteRepository';
import { CloudAPIClient } from './CloudAPIClient';
import { NoteEntity, SyncStatus } from '../model/NoteEntity';
export class SyncManager {
private repo: NoteRepository = new NoteRepository();
private cloud: CloudAPIClient = new CloudAPIClient();
private isSyncing: boolean = false;
// 启动后台同步
async startSync(): Promise<void> {
if (this.isSyncing) return;
this.isSyncing = true;
try {
const pendingNotes = await this.repo.getPendingNotes();
for (const note of pendingNotes) {
await this.syncSingleNote(note);
}
} finally {
this.isSyncing = false;
}
}
private async syncSingleNote(note: NoteEntity): Promise<void> {
try {
// 标记为同步中
await this.repo.dao.update(note.id, { syncStatus: SyncStatus.SYNCING });
// 上传到云端
const cloudNote = await this.cloud.uploadNote({
id: note.cloudId || undefined,
title: note.title,
content: note.content,
updateTime: note.updateTime,
version: note.version
});
// 云端返回更新后的数据
if (cloudNote.version > note.version) {
// 云端有更新,拉取最新内容
await this.repo.dao.update(note.id, {
title: cloudNote.title,
content: cloudNote.content,
version: cloudNote.version,
syncStatus: SyncStatus.SYNCED,
cloudId: cloudNote.id
});
} else {
// 本地为最新,标记同步成功
await this.repo.markSynced(note.id, cloudNote.id);
}
} catch (e) {
console.error('同步失败:', e);
await this.repo.dao.update(note.id, { syncStatus: SyncStatus.FAILED });
}
}
// 拉取云端更新(定时调用或收到推送时)
async pullCloudUpdates(): Promise<void> {
const lastSyncTime = await this.getLastSyncTime();
const cloudUpdates = await this.cloud.fetchUpdates(lastSyncTime);
for (const update of cloudUpdates) {
const localNote = await this.repo.dao.queryBuilder()
.where('cloudId', '=', update.id)
.getOne();
if (!localNote) {
// 本地不存在,插入新笔记
const newNote = new NoteEntity();
newNote.cloudId = update.id;
newNote.title = update.title;
newNote.content = update.content;
newNote.updateTime = update.updateTime;
newNote.syncStatus = SyncStatus.SYNCED;
await this.repo.dao.insert(newNote);
} else if (update.updateTime > localNote.updateTime) {
// 云端更新,覆盖本地
await this.repo.dao.update(localNote.id, {
title: update.title,
content: update.content,
updateTime: update.updateTime,
syncStatus: SyncStatus.SYNCED,
version: update.version
});
}
}
await this.setLastSyncTime(Date.now());
}
}
4.4 冲突解决策略
// sync/ConflictResolver.ets
export class NoteConflictResolver {
// 自动合并策略:标题和内容分别取最新
static autoMerge(local: NoteEntity, remote: any): Partial<NoteEntity> {
return {
title: remote.updateTime > local.updateTime ? remote.title : local.title,
content: remote.updateTime > local.updateTime ? remote.content : local.content,
updateTime: Math.max(local.updateTime, remote.updateTime),
syncStatus: SyncStatus.SYNCED,
version: Math.max(local.version, remote.version) + 1
};
}
// 人工选择策略:保留两端数据供用户选择
static createConflictRecord(local: NoteEntity, remote: any): ConflictRecord {
return {
noteId: local.id,
localVersion: { title: local.title, content: local.content, time: local.updateTime },
remoteVersion: { title: remote.title, content: remote.content, time: remote.updateTime },
suggested: this.autoMerge(local, remote)
};
}
}
interface ConflictRecord {
noteId: number;
localVersion: { title: string; content: string; time: number };
remoteVersion: { title: string; content: string; time: number };
suggested: Partial<NoteEntity>;
}
五、数据迁移实战:从6.1到7.0
5.1 6.1 原始数据库结构
// 6.1 的旧表结构(需迁移)
// notes 表:id, title, content, create_time
// 7.0 新表需增加:update_time, sync_status, version, cloud_id
5.2 迁移脚本
// 7.0 推演:自定义迁移
@arkORM.Migration({
fromVersion: 1,
toVersion: 2,
steps: [
{
type: 'addColumn',
table: 'notes',
column: { name: 'update_time', type: 'INTEGER', default: 0 }
},
{
type: 'addColumn',
table: 'notes',
column: { name: 'sync_status', type: 'INTEGER', default: 0 }
},
{
type: 'addColumn',
table: 'notes',
column: { name: 'version', type: 'INTEGER', default: 1 }
},
{
type: 'addColumn',
table: 'notes',
column: { name: 'cloud_id', type: 'TEXT', default: '' }
},
{
type: 'executeSql',
sql: 'UPDATE notes SET update_time = create_time'
}
]
})
class MigrationV1ToV2 {}
六、性能与安全优化
6.1 查询优化
| 优化手段 | 实现方式 | 效果 |
|---|---|---|
| 索引 | @arkORM.Index(['sync_status', 'update_time']) |
待同步查询从 O(n) 降至 O(log n) |
| 分页 | .offset(100).limit(20) |
避免大数据量一次性加载 |
| 预编译 | DAO 层缓存 SQL 语句 | 减少 SQL 解析开销 |
| 批量操作 | insertMany() / updateMany() |
减少事务提交次数 |
6.2 数据安全
// 数据库加密配置
@arkORM.Database({
name: 'note_db',
encryption: {
enabled: true,
algorithm: 'AES-256-GCM',
keyProvider: 'system_keystore' // 使用系统 KeyStore 管理密钥
}
})
class SecureNoteDatabase {}
七、结语
离线优先是现代移动应用的基础架构范式——用户期待的是"无论有无网络,应用都能正常工作"。HarmonyOS 6.x 的 relationalStore 已经提供了坚实的数据库底座,但工程化的 ORM、自动迁移和同步管道仍需开发者自行搭建。
HarmonyOS 7.0 若真如推演般引入 ArkORM 原生支持,将大幅降低数据层的开发门槛。开发者不再需要手写 SQL 和迁移脚本,而是专注于业务模型的设计和同步策略的调优。
对于高校学生开发者,理解离线优先架构不仅有助于完成高质量的毕业设计,更是培养"端到端系统思维"的绝佳训练——你需要同时考虑本地存储、网络传输、冲突解决、数据安全和用户体验,这些能力在任何技术栈中都是稀缺且高价值的。
转载自:https://blog.csdn.net/u014727709/article/details/162933402
欢迎 👍点赞✍评论⭐收藏,欢迎指正
更多推荐



所有评论(0)