在这里插入图片描述

每日一句正能量

跨过这段路,幸福就在拐角处。
不说“一切都会好起来”这种空话,而是把希望放在一个近在眼前的位置(拐角)。让痛苦有尽头感,人就能再多走几步。

导读

在指导学生开发需要离线功能的鸿蒙应用(如笔记、待办、阅读器)时,数据持久化始终是核心课题。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 {}

自动迁移的工作原理

  1. 对比当前实体类定义与数据库实际 Schema;
  2. 生成差异 DDL(ALTER TABLECREATE INDEXDROP COLUMN 等);
  3. 在事务中按顺序执行迁移脚本;
  4. 迁移失败时自动回滚,保障数据安全。

三、离线优先架构设计

3.1 架构分层

图1:离线优先数据库架构分层图

图片内容说明(中文):纵向四层结构。最上层"UI层":ArkUI组件,通过ViewModel访问数据。第二层"业务逻辑层":ViewModel、Repository、SyncManager。第三层"数据访问层":ArkORM DAO、本地数据库(SQLite)、云端API客户端。第四层"存储层":本地闪存(加密)、云端数据库(华为云/自建)、分布式数据对象(可选)。各层之间用箭头标注依赖方向,右侧标注"UI层不直接访问数据库"。

存储层

数据访问层

业务逻辑层

UI 层

NoteEditor
笔记编辑页

NoteList
笔记列表页

SyncPanel
同步状态页

ViewModel

NoteRepository
数据仓库

SyncManager
同步管理器

ArkORM DAO

本地数据库
SQLite

CloudAPIClient
云端接口

本地闪存
TEE加密

云端数据库

分布式数据对象
可选

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多对多关联。各字段标注数据类型和约束。

has

belongs_to

NOTE

INTEGER

id

PK

自增主键

TEXT

title

标题,非空

TEXT

content

内容,Markdown

INTEGER

create_time

创建时间戳

INTEGER

update_time

更新时间戳

INTEGER

sync_status

同步状态:0待同步1已同步2冲突

INTEGER

version

乐观锁版本号

NOTE_TAG

INTEGER

note_id

PK,FK

笔记ID

INTEGER

tag_id

PK,FK

标签ID

TAG

INTEGER

id

PK

自增主键

TEXT

name

标签名,唯一

TEXT

color

颜色值

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
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐