HarmonyOS鸿蒙Next中通过继承机制模仿后端优雅的实现CRUD

HarmonyOS鸿蒙Next中通过继承机制模仿后端优雅的实现CRUD 通过继承机制模仿后端优雅的实现CRUD

7 回复

实现思路:

在后端开发中,有很多开源的CRUD框架,在鸿蒙Arkts中,我们通过继承的方式,进行模版化开发,通过固定的模版再加上AI,能够快速提升代码的生成效率;

具体如下,定义一个数据表的基础接口 IBasePO:

图片

通过抽象AbstractBasePO实现接口:

图片

具体的PO去实现这个抽象类,后续通过这个规范,用AI根据表结构设计批量生成PO

图片

接下来开始实现DAO层(后端开发做久了,什么都想分层处理😭)

定义基础的接口:

图片

然后通过抽象类来实现这些接口:

图片

为了解决Arkts没有重载和反射这些后端特性,我们做了以上的处理,也是为了方便后续通过AI进行优化的代码批量生成。

接下来开始写业务Dao层:

图片

通过继承抽象类,所有的基础增删改查都不用重复去写代码了,后续抽象类可以通过实现更多的通用查询,来减少业务DAO层的重复代码。(代码强迫症,如果有更好的实现方式或特性,记得留言告诉我)

完整的代码示例:

/**
 * @author: 萌狼
 * @date: 2025-11-12
 * @version: 2.0.0
 * @description: 文档库数据访问层
 * 
 * 职责:
 * - 纯数据库操作(CRUD)
 * - 不包含业务逻辑
 * - 不管理事务(由 Service 层管理)
 * - 不记录变更日志(由 Service 层管理)
 */
export class LibraryDao extends AbstractBaseDao<LibraryPO> {
  /**
   * 获取表名
   * @returns 数据库表名
   */
  protected getTableName(): string {
    return 'U_LIBRARY';
  }
}

/**
 * @author: 萌狼
 * @date: 2025-11-02
 * @version: 1.0.0
 * @description:
*
* ArkTS 设计说明:
 * 由于 ArkTS 不支持函数重载,且静态属性无法通过泛型获取,
 * 因此采用抽象方法 getTableName() 让子类提供表名。
 * 这样既避免了每个方法都传递实例对象,又保持了代码的优雅性。
 */
export abstract class AbstractBaseDao<T extends AbstractBasePO> implements IBaseDao<T> {
  /**
   * 插入数据
   * @param po
   * @returns 返回行ID
   */
  insert(po: T): number {
    let result = 0;
    const valueBucket: relationalStore.ValuesBucket = po.toInsertRecord();
    try {
      result = RdbStoreUtil.getRdbStore()?.insertSync(this.getTableName(), valueBucket) ?? 0;
    } catch (err) {
      console.error(`[AbstractBaseDao] insert failed: ${err}`);
    }
    return result;
  }

  /**
   * 更新数据
   * @param id 记录ID
   * @param po 持久化对象
   * @author 萌狼
   */
  update(po: T): number {
    let result = 0;
    let predicates = new relationalStore.RdbPredicates(this.getTableName());
    predicates.equalTo('ID', po.id);
    if (po.isUserFilter()) {
      predicates.equalTo('USER_ID', po.userId);
    }

    try {
      result = RdbStoreUtil.getRdbStore()?.updateSync(po.toUpdateRecord(), predicates) ?? 0;
    } catch (err) {
      //Logger.error(EventLogCode.EL80020012, err.code, err.message);
    }

    return result;
  }

  /**
   * 根据ID查询记录
   *
   * @param id 记录ID
   * @returns T | null 查询结果,不存在返回 null
   */
  findById(id: string): T | null {
    try {
      const predicates = new relationalStore.RdbPredicates(this.getTableName());
      predicates.equalTo('ID', id);

      const resultSet = RdbStoreUtil.getRdbStore()?.querySync(predicates);

      if (resultSet && resultSet.goToFirstRow()) {
        const mapResult = this.mapResultSetToPO(resultSet);
        const po: T = JSON.parse(JSON.stringify(mapResult));
        resultSet.close();
        return po;
      }

      if (resultSet) {
        resultSet.close();
      }
      return null;
    } catch (error) {
      console.error(`[AbstractBaseDao] findById failed: ${error}`);
      return null;
    }
  }

  /**
   * 查询所有数据
   * @returns T[] 查询结果列表
   * @author 萌狼
   */
  findAll(): T[] {
    const resultList: Array<T> = [];
    try {
      const predicates = new relationalStore.RdbPredicates(this.getTableName());
      predicates.equalTo('IS_DELETED', 0);
      predicates.equalTo('USER_ID', AppContextUtil.getUserId());

      const resultSet = RdbStoreUtil.getRdbStore()?.querySync(predicates);
      if (resultSet) {
        while (resultSet.goToNextRow()) {
          const mapResult = this.mapResultSetToPO(resultSet);
          const po: T = JSON.parse(JSON.stringify(mapResult));
          resultList.push(po);
        }
        resultSet.close();
      }
    } catch (error) {
      console.error(`[AbstractBaseDao] findAll failed: ${error}`);
    }
    return resultList;
  }

  /**
   * 获取表名
   * 子类必须实现此方法,返回对应的数据库表名
   * @returns 数据库表名
   */
  protected abstract getTableName(): string;

  /**
   * 获取自定义字段映射(可选)
   *
   * 子类可以覆盖此方法,提供自定义的数据库字段名到PO字段名的映射
   * 如果返回 null,则使用默认的 dbColumnToCamelCase 自动转换
   *
   * @returns Record<string, string> | null
   *   - key: 数据库字段名(如 'RESOURCE_ID')
   *   - value: PO字段名(如 'resourceId')
   *
   * @example
  * protected getFieldMapping(): Record<string, string> | null {
   *   return {
   *     'RESOURCE_ID': 'resourceId',
   *     'USER_ID': 'userId'
   *   };
   * }
   */
  protected getFieldMapping(): Record<string, string> | null {
    return null; // 默认使用自动转换
  }

  /**
   * 数据库字段名转驼峰命名
   *
   * 转换规则:
   * - RESOURCE_ID → resourceId
   * - USER_ID → userId
   * - ID → id
   * - CREATED_TIME → createdTime
   *
   * @param columnName 数据库字段名(大写+下划线)
   * @returns 驼峰命名的字段名
   */
  protected dbColumnToCamelCase(columnName: string): string {
    return columnName
      .toLowerCase()                    // RESOURCE_ID → resource_id
      .split('_')                       // ['resource', 'id']
      .map((word, index) => {
        if (index === 0) {
          return word;                  // 第一个单词保持小写
    }
        return word.charAt(0).toUpperCase() + word.slice(1); // 首字母大写
      })
      .join('');                        // resourceId
  }

  /**
   * 将数据库记录转换为PO对象
   *
   * 优先使用自定义映射 getFieldMapping(),
   * 如果没有自定义映射,则使用自动转换 dbColumnToCamelCase()
   *
   * @param resultSet 查询结果集
   * @returns Record<string, ValueType> PO字段映射
   */
  protected mapResultSetToPO(resultSet: relationalStore.ResultSet): Record<string, relationalStore.ValueType> {
    const columnNames = resultSet.columnNames;
    const mapResult: Record<string, relationalStore.ValueType> = {};
    const customMapping = this.getFieldMapping();

    for (let i = 0; i < columnNames.length; i++) {
      const dbColumn = columnNames[i];
      const poField = customMapping && customMapping[dbColumn] ?
        customMapping[dbColumn] :
        this.dbColumnToCamelCase(dbColumn);

      mapResult[poField] = resultSet.getValue(resultSet.getColumnIndex(dbColumn));
    }

    return mapResult;
  }
}

/**
 * @author: 萌狼
 * @date: 2025-11-01
 * @version: 1.0.0
 * @description:
 */
export interface IBaseDao<T extends AbstractBasePO> {
  /**
   * 插入数据
   * @param po
   * @returns 返回行ID
   */
  insert(po: T): number;
  /**
   * 更新数据
   * @param id 记录ID
   * @param po 持久化对象
   * @author 萌狼
   */
  update(po: T): number;

  /**
   * 根据ID查询记录
   *
   * @param id 记录ID
   * @returns T | null 查询结果,不存在返回 null
   */
  findById(id: string): T | null;
}

总结:

代码还有很多可以改进的地方,后续会持续去优化,如果反馈的人数多,会考虑做成一个开源框架,让大家更多的从关注业务数据库层转为关注业务服务层,如果Arkts能够支持反射就好了,很多代码都可以通过反射去处理,包括JSON格式可以通过注解来实现序列化和反序列化,如果原生已经支持的话记得踢一脚博主哈。

为什么会想去实现这一套,是因为通过模版化的开发,我们后面就可以很简单的用AI来帮我们做这种模版化开发,包括批量生成PO、DAO层的代码,特性化的可以自己去实现。

更多关于HarmonyOS鸿蒙Next中通过继承机制模仿后端优雅的实现CRUD的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


不错👍👍👍,给个小小的建议。可以封装一下然后发布到 ohpm 三方库里去,这样更方便使用。

https://ohpm.openharmony.cn/#/cn/home

你这是自问自答了吗

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

活动举办方要求自问自答的格式🤣,

在HarmonyOS Next中,可通过继承基类实现CRUD。定义抽象基类如BaseDao<T>,封装通用数据库操作方法。子类继承并指定泛型类型,实现具体数据模型的操作。使用ArkTS编写,利用系统提供的数据库API进行数据持久化。这种方式减少重复代码,提升开发效率。

在HarmonyOS Next中,通过继承机制实现CRUD操作是一种符合ArkTS面向对象特性的设计模式,可以提升代码复用性和结构清晰度。以下是一种典型的实现思路:

1. 定义基础数据模型

// BaseModel.ets
export class BaseModel {
  id?: string; // 通用ID字段
  createdAt?: number; // 创建时间戳
  updatedAt?: number; // 更新时间戳
}

2. 创建通用Repository基类

// BaseRepository.ets
export abstract class BaseRepository<T extends BaseModel> {
  protected abstract tableName: string;
  
  // 通用CRUD方法
  async create(item: T): Promise<boolean> {
    // 统一设置时间戳
    const now = Date.now();
    item.createdAt = now;
    item.updatedAt = now;
    
    // 实际数据库操作
    return await this.saveToDB(item);
  }
  
  async update(id: string, updates: Partial<T>): Promise<boolean> {
    updates.updatedAt = Date.now();
    return await this.updateInDB(id, updates);
  }
  
  async delete(id: string): Promise<boolean> {
    return await this.deleteFromDB(id);
  }
  
  async findById(id: string): Promise<T | null> {
    return await this.queryFromDB(id);
  }
  
  // 抽象方法由子类实现具体存储逻辑
  protected abstract saveToDB(item: T): Promise<boolean>;
  protected abstract updateInDB(id: string, updates: Partial<T>): Promise<boolean>;
  protected abstract deleteFromDB(id: string): Promise<boolean>;
  protected abstract queryFromDB(id: string): Promise<T | null>;
}

3. 具体业务Repository实现

// UserRepository.ets
import { BaseRepository } from './BaseRepository';
import { User } from '../model/User';

export class UserRepository extends BaseRepository<User> {
  protected tableName: string = 'users';
  
  // 实现具体的数据库操作
  protected async saveToDB(user: User): Promise<boolean> {
    // 使用HarmonyOS DataAbility或RDB具体实现
    const predicates = new dataRdb.RdbPredicates(this.tableName);
    // ... 具体插入逻辑
    return true;
  }
  
  protected async updateInDB(id: string, updates: Partial<User>): Promise<boolean> {
    // ... 更新逻辑实现
    return true;
  }
  
  // 可以添加业务特有方法
  async findByEmail(email: string): Promise<User | null> {
    // 特定查询逻辑
    return null;
  }
}

4. 业务模型定义

// User.ets
import { BaseModel } from './BaseModel';

export class User extends BaseModel {
  username: string = '';
  email: string = '';
  age?: number;
  
  constructor(username: string, email: string) {
    super();
    this.username = username;
    this.email = email;
  }
}

5. 使用示例

// 业务层使用
const userRepo = new UserRepository();
const newUser = new User('john_doe', 'john@example.com');

// 创建用户
await userRepo.create(newUser);

// 查询用户
const user = await userRepo.findById('123');

// 更新用户
await userRepo.update('123', { age: 30 });

// 特有查询
const userByEmail = await userRepo.findByEmail('john@example.com');

优势:

  • 代码复用:通用CRUD逻辑在基类中一次实现
  • 类型安全:ArkTS泛型确保类型一致性
  • 易于扩展:新增实体只需继承基类
  • 维护简单:数据库操作变更只需修改基类或具体实现

注意事项:

  1. 根据实际存储方案(RDB、对象存储等)调整具体实现
  2. 考虑异步操作的状态管理和错误处理
  3. 复杂查询场景可能需要扩展基类接口

这种模式特别适合HarmonyOS Next中需要本地数据持久化的应用场景,通过良好的分层设计保持代码的可维护性和可测试性。

回到顶部