HarmonyOS鸿蒙Next中在开发应用时,如何设计一个优雅的数据服务层来封装数据库操作?

HarmonyOS鸿蒙Next中在开发应用时,如何设计一个优雅的数据服务层来封装数据库操作? 经常会遇见以下问题,怎样解决比较好:

  1. 数据层和业务层耦合严重
  2. CRUD操作代码重复
  3. 复杂查询难以实现
  4. 数据类型转换繁琐
  5. 批量操作性能差
3 回复

技术要点

  • 数据服务层架构设计
  • 单例模式应用
  • SQL查询优化
  • 批量操作事务处理
  • 数据类型转换封装

完整实现代码

/**
 * 数据服务层
 * 提供业务数据的CRUD操作
 */

import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { 
  HumanRecord, 
  Person, 
  FilterCondition, 
  SortConfig,
  StatisticsData,
  PersonDetail,
  RecordType
} from '../model/DataModels';
import { DatabaseManager } from '../database/DatabaseManager';

export class DataService {
  private static instance: DataService;
  private dbManager: DatabaseManager;

  private constructor() {
    this.dbManager = DatabaseManager.getInstance();
  }

  /**
   * 获取单例实例
   */
  public static getInstance(): DataService {
    if (!DataService.instance) {
      DataService.instance = new DataService();
    }
    return DataService.instance;
  }

  /**
   * 初始化数据服务
   */
  public async init(context: common.UIAbilityContext): Promise<void> {
    await this.dbManager.initDatabase(context);
  }

  // ==================== 人物管理 ====================

  /**
   * 添加人物
   */
  public async addPerson(person: Person): Promise<string> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    const id = person.id || this.generateId();
    const now = Date.now();
    
    const insertSql = `
      INSERT INTO lelv_persons 
      (id, name, relationship_type, relationship_tags, phone, avatar, contact_id, create_time, update_time)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
    `;

    await store.executeSql(insertSql, [
      id,
      person.name,
      person.relationshipType,
      JSON.stringify(person.relationshipTags),
      person.phone || null,
      person.avatar || null,
      person.contactId || null,
      person.createTime || now,
      now
    ]);

    return id;
  }

  /**
   * 更新人物信息
   */
  public async updatePerson(id: string, person: Person): Promise<void> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    const updateSql = `
      UPDATE lelv_persons 
      SET name = ?, relationship_type = ?, relationship_tags = ?, phone = ?, avatar = ?, update_time = ?
      WHERE id = ?
    `;

    await store.executeSql(updateSql, [
      person.name,
      person.relationshipType,
      JSON.stringify(person.relationshipTags),
      person.phone || null,
      person.avatar || null,
      Date.now(),
      id
    ]);
  }

  /**
   * 删除人物
   */
  public async deletePerson(id: string): Promise<void> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    // 先删除相关的人情记录
    await store.executeSql('DELETE FROM lelv_human_records WHERE person_id = ?', [id]);
    // 再删除人物
    await store.executeSql('DELETE FROM lelv_persons WHERE id = ?', [id]);
  }

  /**
   * 获取所有人物
   */
  public async getAllPersons(): Promise<Person[]> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    const resultSet = await store.querySql('SELECT * FROM lelv_persons ORDER BY create_time DESC');
    return this.parsePersonsFromResultSet(resultSet);
  }

  /**
   * 根据ID获取人物
   */
  public async getPersonById(id: string): Promise<Person | null> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    const resultSet = await store.querySql('SELECT * FROM lelv_persons WHERE id = ?', [id]);
    const persons = this.parsePersonsFromResultSet(resultSet);
    return persons.length > 0 ? persons[0] : null;
  }

  // ==================== 人情记录管理 ====================

  /**
   * 添加人情记录
   */
  public async addRecord(record: HumanRecord): Promise<string> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    const id = record.id || this.generateId();
    const now = Date.now();
    
    const insertSql = `
      INSERT INTO lelv_human_records 
      (id, type, event_type, custom_event_type, amount, event_time, person_id, location, remark, photos, custom_fields, create_time, update_time)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `;

    await store.executeSql(insertSql, [
      id,
      record.type,
      record.eventType,
      record.customEventType || null,
      record.amount,
      record.eventTime,
      record.personId,
      record.location || null,
      record.remark || null,
      JSON.stringify(record.photos || []),
      this.stringifyCustomFields(record.customFields),
      record.createTime || now,
      now
    ]);

    return id;
  }

  /**
   * 获取所有人情记录(支持筛选和排序)
   */
  public async getAllRecords(filter?: FilterCondition, sort?: SortConfig): Promise<HumanRecord[]> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    let sql = `
      SELECT hr.*, p.name as person_name, p.relationship_type, p.avatar
      FROM lelv_human_records hr
      LEFT JOIN lelv_persons p ON hr.person_id = p.id
      WHERE 1=1
    `;
    const params: (string | number)[] = [];

    // 应用筛选条件
    if (filter) {
      if (filter.timeRange) {
        sql += ' AND hr.event_time BETWEEN ? AND ?';
        params.push(filter.timeRange.start, filter.timeRange.end);
      }
      if (filter.eventTypes && filter.eventTypes.length > 0) {
        const placeholders = filter.eventTypes.map(() => '?').join(',');
        sql += ` AND hr.event_type IN (${placeholders})`;
        params.push(...filter.eventTypes);
      }
      if (filter.relationshipTypes && filter.relationshipTypes.length > 0) {
        const placeholders = filter.relationshipTypes.map(() => '?').join(',');
        sql += ` AND p.relationship_type IN (${placeholders})`;
        params.push(...filter.relationshipTypes);
      }
      if (filter.amountRange) {
        sql += ' AND hr.amount BETWEEN ? AND ?';
        params.push(filter.amountRange.min, filter.amountRange.max);
      }
      if (filter.keywords) {
        sql += ' AND (hr.remark LIKE ? OR p.name LIKE ?)';
        const keyword = `%${filter.keywords}%`;
        params.push(keyword, keyword);
      }
    }

    // 应用排序
    if (sort) {
      let orderField = 'hr.create_time';
      switch (sort.field) {
        case 'eventTime':
          orderField = 'hr.event_time';
          break;
        case 'amount':
          orderField = 'hr.amount';
          break;
        case 'name':
          orderField = 'p.name';
          break;
      }
      sql += ` ORDER BY ${orderField} ${sort.order.toUpperCase()}`;
    } else {
      sql += ' ORDER BY hr.create_time DESC';
    }

    const resultSet = await store.querySql(sql, params);
    return this.parseRecordsFromResultSet(resultSet);
  }

  // ==================== 统计功能 ====================

  /**
   * 获取统计数据
   */
  public async getStatistics(): Promise<StatisticsData> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    // 获取总收入
    const receivedResult = await store.querySql(`
      SELECT COALESCE(SUM(amount), 0) as total
      FROM lelv_human_records 
      WHERE type = 'received'
    `);

    // 获取总支出
    const sentResult = await store.querySql(`
      SELECT COALESCE(SUM(amount), 0) as total
      FROM lelv_human_records 
      WHERE type = 'sent'
    `);

    // 获取记录总数
    const countResult = await store.querySql(`
      SELECT COUNT(*) as count FROM lelv_human_records
    `);

    // 获取人物总数
    const personCountResult = await store.querySql(`
      SELECT COUNT(DISTINCT person_id) as count FROM lelv_human_records
    `);

    let totalReceived = 0;
    if (receivedResult.goToFirstRow()) {
      totalReceived = receivedResult.getDouble(0);
    }

    let totalSent = 0;
    if (sentResult.goToFirstRow()) {
      totalSent = sentResult.getDouble(0);
    }

    let recordCount = 0;
    if (countResult.goToFirstRow()) {
      recordCount = countResult.getLong(0);
    }

    let personCount = 0;
    if (personCountResult.goToFirstRow()) {
      personCount = personCountResult.getLong(0);
    }

    return {
      totalReceived,
      totalSent,
      netAmount: totalReceived - totalSent,
      recordCount,
      personCount,
      monthlyTrend: await this.getMonthlyTrend()
    };
  }

  /**
   * 获取个人往来详情
   */
  public async getPersonDetail(personId: string): Promise<PersonDetail | null> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    const person = await this.getPersonById(personId);
    if (!person) return null;

    // 获取该人物的所有记录
    const resultSet = await store.querySql(`
      SELECT * FROM lelv_human_records 
      WHERE person_id = ? 
      ORDER BY event_time DESC
    `, [personId]);

    const records = this.parseRecordsFromResultSet(resultSet);
    
    // 计算统计数据
    const totalReceived = records
      .filter(r => r.type === RecordType.RECEIVED)
      .reduce((sum, r) => sum + r.amount, 0);
    
    const totalSent = records
      .filter(r => r.type === RecordType.SENT)
      .reduce((sum, r) => sum + r.amount, 0);

    return {
      person,
      totalReceived,
      totalSent,
      netAmount: totalReceived - totalSent,
      recordCount: records.length,
      lastRecordTime: records.length > 0 ? records[0].eventTime : 0,
      records
    };
  }

  // ==================== 批量操作 ====================

  /**
   * 批量添加记录
   */
  public async batchAddRecords(records: HumanRecord[]): Promise<string[]> {
    const store = this.dbManager.getStore();
    if (!store) throw new Error('数据库未初始化');

    const ids: string[] = [];
    const now = Date.now();

    try {
      // 开启事务
      await store.beginTransaction();
      
      for (const record of records) {
        const id = this.generateId();
        const insertSql = `
          INSERT INTO lelv_human_records 
          (id, type, event_type, custom_event_type, amount, event_time, person_id, location, remark, photos, custom_fields, create_time, update_time)
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        `;

        await store.executeSql(insertSql, [
          id,
          record.type,
          record.eventType,
          record.customEventType || null,
          record.amount,
          record.eventTime,
          record.personId,
          record.location || null,
          record.remark || null,
          JSON.stringify(record.photos),
          this.stringifyCustomFields(record.customFields),
          now,
          now
        ]);
        
        ids.push(id);
      }
      
      // 提交事务
      await store.commit();
      console.info(`批量添加记录成功: ${ids.length}条`);
      return ids;
    } catch (error) {
      // 回滚事务
      await store.rollback(void 0);
      console.error('批量添加记录失败:', JSON.stringify(error));
      throw new Error('批量添加记录失败');
    }
  }

  // ==================== 工具方法 ====================

  /**
   * 生成唯一ID
   */
  private generateId(): string {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }

  /**
   * 解析人物数据
   */
  private parsePersonsFromResultSet(resultSet: relationalStore.ResultSet): Person[] {
    const persons: Person[] = [];
    while (resultSet.goToNextRow()) {
      persons.push({
        id: resultSet.getString(0),
        name: resultSet.getString(1),
        relationshipType: resultSet.getString(2),
        relationshipTags: JSON.parse(resultSet.getString(3) || '[]'),
        phone: resultSet.getString(4),
        avatar: resultSet.getString(5),
        contactId: resultSet.getString(6),
        createTime: resultSet.getLong(7),
        updateTime: resultSet.getLong(8)
      });
    }
    resultSet.close();
    return persons;
  }

  /**
   * 解析记录数据
   */
  private parseRecordsFromResultSet(resultSet: relationalStore.ResultSet): HumanRecord[] {
    const records: HumanRecord[] = [];
    
    while (resultSet.goToNextRow()) {
      const record: HumanRecord = {
        id: resultSet.getString(0),
        type: resultSet.getString(1) as RecordType,
        eventType: resultSet.getString(2),
        customEventType: resultSet.getString(3),
        amount: resultSet.getDouble(4),
        eventTime: resultSet.getLong(5),
        personId: resultSet.getString(6),
        location: resultSet.getString(7),
        remark: resultSet.getString(8),
        photos: JSON.parse(resultSet.getString(9) || '[]'),
        customFields: this.parseCustomFields(resultSet.getString(10) || '{}'),
        createTime: resultSet.getLong(11),
        updateTime: resultSet.getLong(12)
      };
      
      // 尝试获取JOIN的关联字段
      try {
        record.personName = resultSet.getString(13);
        record.relationshipType = resultSet.getString(14);
        record.avatar = resultSet.getString(15);
      } catch (error) {
        // 如果没有JOIN字段,忽略
      }
      
      records.push(record);
    }
    resultSet.close();
    return records;
  }

  /**
   * 序列化自定义字段
   */
  private stringifyCustomFields(customFields: Map<string, string> | Record<string, string> | undefined): string {
    if (!customFields) return '[]';
    
    if (customFields instanceof Map) {
      return JSON.stringify(Array.from(customFields.entries()));
    }
    
    if (typeof customFields === 'object') {
      return JSON.stringify(Object.entries(customFields));
    }
    
    return '[]';
  }

  /**
   * 解析自定义字段
   */
  private parseCustomFields(jsonStr: string): Map<string, string> {
    try {
      const entries: [string, string][] = JSON.parse(jsonStr);
      const map = new Map<string, string>();
      if (Array.isArray(entries)) {
        entries.forEach(entry => map.set(entry[0], entry[1]));
      }
      return map;
    } catch (error) {
      return new Map<string, string>();
    }
  }

  /**
   * 获取月度趋势数据
   */
  private async getMonthlyTrend(): Promise<any[]> {
    const store = this.dbManager.getStore();
    if (!store) return [];

    const resultSet = await store.querySql(`
      SELECT 
        strftime('%Y-%m', datetime(event_time/1000, 'unixepoch')) as month,
        SUM(CASE WHEN type = 'received' THEN amount ELSE 0 END) as received,
        SUM(CASE WHEN type = 'sent' THEN amount ELSE 0 END) as sent
      FROM lelv_human_records 
      WHERE event_time >= ?
      GROUP BY month
      ORDER BY month DESC
      LIMIT 12
    `, [Date.now() - 365 * 24 * 60 * 60 * 1000]);

    const trends: any[] = [];
    while (resultSet.goToNextRow()) {
      trends.push({
        month: resultSet.getString(0),
        received: resultSet.getDouble(1),
        sent: resultSet.getDouble(2),
        net: resultSet.getDouble(1) - resultSet.getDouble(2)
      });
    }
    resultSet.close();
    return trends;
  }
}

核心设计思想

1. 分层架构

UI层 (Pages)
  ↓
业务服务层 (Services)
  ↓
数据服务层 (DataService) ← 本文重点
  ↓
数据库层 (DatabaseManager)

2. 单例模式

确保全局只有一个DataService实例,统一管理数据访问。

3. 统一的错误处理

if (!store) throw new Error('数据库未初始化');

4. 数据类型转换封装

  • JSON序列化/反序列化
  • Map与数组的相互转换
  • ResultSet到业务对象的映射

最佳实践

1. 复杂查询使用LEFT JOIN

SELECT hr.*, p.name as person_name
FROM lelv_human_records hr
LEFT JOIN lelv_persons p ON hr.person_id = p.id

2. 参数化查询防止SQL注入

await store.querySql('SELECT * FROM persons WHERE id = ?', [id]);

3. 批量操作使用事务

await store.beginTransaction();
// ... 批量操作
await store.commit();

4. 记得关闭ResultSet

resultSet.close();

性能优化技巧

  1. 使用索引: 在常用查询字段上创建索引
  2. 减少JOIN: 简单查询避免不必要的JOIN
  3. 分页加载: 大数据量使用LIMIT和OFFSET
  4. 缓存策略: 频繁访问的数据使用内存缓存

避坑指南

更多关于HarmonyOS鸿蒙Next中在开发应用时,如何设计一个优雅的数据服务层来封装数据库操作?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,可通过Data Ability Kit封装数据库操作。使用关系型数据库(RDB)或对象关系映射(ORM)框架管理数据,结合DataShare Extension Ability实现跨应用数据共享。设计时采用分层架构:数据访问层负责CRUD操作,业务逻辑层处理数据转换,服务层提供统一API。利用HarmonyOS分布式特性,通过分布式数据对象实现设备间数据同步。确保数据操作异步化,避免阻塞UI线程。

在HarmonyOS Next中设计优雅的数据服务层,建议采用以下架构方案:

  1. 分层设计
  • 使用Repository模式隔离数据源与业务逻辑
  • 定义清晰的Entity数据实体和Domain领域模型
  • 通过Mapper实现Entity与Domain对象转换
  1. 统一数据访问接口
  • 封装BaseRepository提供通用CRUD操作
  • 使用泛型约束数据类型,避免重复代码
  • 示例:
abstract class BaseRepository<T> {
  abstract create(item: T): Promise<void>;
  abstract update(item: T): Promise<void>;
  abstract delete(id: string): Promise<void>;
  abstract findById(id: string): Promise<T | null>;
}
  1. 复杂查询处理
  • 使用Specification模式封装查询条件
  • 实现QueryBuilder构建动态查询
  • 支持分页、排序等通用查询需求
  1. 数据类型转换
  • 在Mapper层集中处理类型转换
  • 使用工厂模式创建领域对象
  • 利用TypeScript类型约束确保类型安全
  1. 批量操作优化
  • 实现BatchExecutor处理批量写入
  • 使用事务保证数据一致性
  • 通过连接池管理数据库连接
  1. 异步处理
  • 统一使用Promise/async-await处理异步操作
  • 实现重试机制应对临时故障

这种架构能有效解耦数据层与业务层,提高代码复用性,同时保持良好性能。

回到顶部