【Harmony OS 6】IBest-ORM库使用详解(一)

背景

IBest-ORM是一个轻量、简单易用、全功能、支持实体关联、事务、自动迁移的鸿蒙开源ORM工具库,可以提高开发者对于数据库操作的开发效率。

部署

在DevEco的终端中执行下面的命令,部署第三方库

arkts 复制代码
ohpm install @ibestservices/ibest-orm

初始化

在EntryAbility的onWindowStageCreate方法中进行初始化。

下面为IBestORMInitOptions属性解析

属性名字 类型 备注
name string 数据库文件名,默认为:ibest.db
debug boolean 是否开启调试日志,默认 true
logLevel LogLevel 日志等级
arkts 复制代码
  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    await IBestORMInit(this.context, {
      name: 'Education3.db'
    })

    windowStage.loadContent('pages/EducationPage', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

模型定义

通过装饰器的方式去定义数据库模型

@Table

标记类为数据实体,可以自动判断类的名字,然后转成蛇形命名(使用下划线,一般小写的命名方法,如:teacher_details)的方式

arkts 复制代码
@Table()  // 自动推断表名(类名转蛇形)

@Table({ name: 'custom_table' })  // 自定义表名

@PrimaryKey

标记属性,定义为主键字段,默认自增(需要是number类型),如果使用GUID字符串来做主键的话,需要把自增关掉。

arkts 复制代码
@PrimaryKey()  // 默认自增
id?: number;

@PrimaryKey({ autoIncrement: false })  // 非自增主键
id?: number;

@Column

标记属性,定义为数据表普通列,可以通过自定义的方式,指定名字、类型、非空约束、默认值。

  • type:ColumnType类型。现在仅支持INTEGER(整数)、REAL(浮点数,小数)、TEXT(文本类型)、BLOB(Uint8Array类型)
  • name:自定义列名称。自动识别时,会自动转换成蛇形命名。
  • notNull:不为null。
  • defaultValue:默认值
arkts 复制代码
// 自动推断类型
@Column()
name?: string;

// 显式指定类型
@Column({ type: ColumnType.INTEGER })
age?: number;

// 自定义列名
@Column({ name: 'user_name' })
name?: string;

// 非空约束
@Column({ notNull: true })
email?: string;

// 默认值
@Column({ defaultValue: 0 })
status?: number;

@NotNull

标记属性,定义当前字段为非空。

arkts 复制代码
@NotNull()
@Column()
name?: string;

@CreatedAt

标记属性,定义当前字段为自动记录创建时间。

arkts 复制代码
@CreatedAt()  // 默认列名 'created_at'
createdAt?: string;

@CreatedAt({ name: 'create_time' })  // 自定义列名
createTime?: string;

@UpdatedAt

标记属性,定义当前字段为自动记录更新时间。

arkts 复制代码
@UpdatedAt()  // 默认列名 'updated_at'
updatedAt?: string;

@SoftDelete

标记属性,定义当前字段为软删除字段。对于重要的数据表,建议添加这个字段,这样子可以对数据进行缓存操作。

arkts 复制代码
@SoftDelete()  // 默认列名 'deleted_at'
deletedAt?: string;

@SoftDelete({ name: 'delete_time' })  // 自定义列名
deleteTime?: string;

字段类型

ColumnType SQLite类型 ArkTS类型
INTEGER INTEGER number
REAL REAL number
TEXT TEXT string
BLOB BLOB Uint8Array

基本使用

ORM框架对象

通过@ibestservices/ibest-orm的getORM 方法获取ORM框架对象。ORM对象是所有增删改查和事务的发起点。

arkts 复制代码
  @Local MyORM: ORM = getORM()

相关方法

  • query:获取查询构建器,传入数据表对象类型来获取查询的数据表对象。
  • table:获取查询构建器,传入数据表名字来获取查询的数据表对象。
  • migrate:数据库迁移。
  • insert:插入实体。
  • save:保存实体,有主键时自动更新,无主键时自动插入数据。
  • delete:删除实体。
  • deleteById:根据ID删除实体。
  • beginTransaction:开启事务。
  • commit:提交事务。
  • rollback:回滚事务。
  • executeSql:执行原生 SQL。
  • getAdapter:获取适配器。
  • close:关闭连接。
  • insertWithRelations:级联插入实体。
  • deleteWithRelations:级联删除实体。
  • hasTable:检查表是否存在。
  • hasColumn:检查列是否存在。
  • getMigrationLogs:获取迁移日志。
  • clearMigrationLogs:清空迁移日志。
  • dropColumn:删除字段(带数据备份)。
  • modifyColumn:修改字段类型。
  • generateRollbackSQL:根据迁移日志生成反向操作的 SQL语句。

迁移表结构

在初始化的时候进行迁移。示例中迁移了三个数据表实体类。Teacher、Student、Course

arkts 复制代码
    this.MyORM.migrate(Teacher)
    this.MyORM.migrate(Student)
    this.MyORM.migrate(Course)

增加数据

使用insert方法直接插入,可以单个实体插入,也可以集合的方式插入。

arkts 复制代码
for (let index = 0; index < 50; index++) {
  const element = new Teacher();
  element.name = `老师 ${index + 1} `
  element.age = Math.floor(Math.random() * (50 - 20 + 1)) + 20
  this.TArray.push(element);
}
this.MyORM.insert(this.TArray);

删除数据

通过delete方法或者deleteById方法进行数据的删除操作。

arkts 复制代码
const deleteTeacher = this.TArray[this.TSelectIndex];
this.MyORM.delete(deleteTeacher);

修改数据

通过save方法来修改数据。

arkts 复制代码
const saveTeacher = this.TArray[this.TSelectIndex];
saveTeacher.name = "我是修改后的老师名字";
this.MyORM.save(saveTeacher);
this.TArray = this.MyORM.query(Teacher).with('courses').find();

查询数据

通过对老师列表的数据变化来呈现查询的效果。

条件查询 - 对象风格条件

arkts 复制代码
//gt:大于 gte:大于等于 lt:小于 lte:小于等于 ne:不等于
//示例:查询年龄在22岁到35岁的老师
this.TArray = this.MyORM.query(Teacher)
  .with('courses')
  .where({ age: { lte: 30 } })
  .where({ age: { gte: 20 } })
  .find();

条件查询 - IN查询

arkts 复制代码
//查询年龄为25和35的老师
this.TArray = this.MyORM.query(Teacher)
  .with('courses')
  .whereIn('age', [25, 35])
  .find()

条件查询 - BETWEEN 查询

arkts 复制代码
//查询年龄在25到35岁的老师,包含25和35岁
this.TArray = this.MyORM.query(Teacher)
  .with('courses')
  .whereBetween('age', 25, 35)
  .find()

条件查询 - LIKE 查询

arkts 复制代码
//查询年龄尾数为5的老师,%5:以5开头,%5%:包含5,5%:以5结尾
this.TArray = this.MyORM.query(Teacher)
  .with('courses')
  .whereLike('age', '5%')
  .find()

选择字段查询

arkts 复制代码
//只查询老师名字
this.TArray = this.MyORM.query(Teacher)
  .with('courses')
  .select('name')
  .find()

查询排序

arkts 复制代码
//老师按年龄排序,asc:升序;desc:降序
this.TArray = this.MyORM.query(Teacher)
  .with('courses')
  .orderBy('age', 'asc')
  .find()

分页查询

arkts 复制代码
// 分页查询,查询第二页的内容,每页10个老师
const page = 2;
const pageSize = 10;
this.TArray = this.MyORM.query(Teacher)
  .with('courses')
  .limit(pageSize)
  .offset((page - 1) * pageSize)
  .find();

完整Demo代码

EntryAbility

arkts 复制代码
  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    await IBestORMInit(this.context, {
      name: 'Education4.db'
    })

    windowStage.loadContent('pages/EducationPage', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

BaseEntity

arkts 复制代码
// 基础接口和类型定义
export interface BaseEntity {
  id?: number;
  createdAt?: string;
  updatedAt?: string;
}

Teacher

arkts 复制代码
import {
  CascadeType,
  Column,
  ColumnType,
  CreatedAt,
  HasMany,
  PrimaryKey,
  Table,
  UpdatedAt
} from "@ibestservices/ibest-orm";
import { BaseEntity } from "./BaseEntity";
import { Course } from "./Course";

@Table()
export class Teacher implements BaseEntity {
  @PrimaryKey()
  id?: number;
  @Column()
  name?: string
  @Column({ type: ColumnType.INTEGER })
  age?: number
  @CreatedAt()
  createdAt?: string;
  @UpdatedAt()
  updatedAt?: string;
  @HasMany(() => Course,{
    foreignKey: 'teacher_id',
    cascade: [CascadeType.Delete]
  })
  courses?: Course[]
}

Student

arkts 复制代码
import { BaseEntity } from "./BaseEntity";
import { Column, CreatedAt, ManyToMany, PrimaryKey, Table, UpdatedAt } from "@ibestservices/ibest-orm";
import { Course } from "./Course";

@Table()
export class Student implements BaseEntity {
  @PrimaryKey()
  id?: number;
  @Column()
  name?: string
  @CreatedAt()
  createdAt?: string;
  @UpdatedAt()
  updatedAt?: string;
  @ManyToMany(() => Course,{
    through: 'student_course',
    throughForeignKey: 'student_id',
    throughOtherKey: 'course_id'
  })
  courses?: Course[]
}

Course

arkts 复制代码
import { BaseEntity } from "./BaseEntity";
import {
  BelongsTo,
  CascadeType,
  Column,
  ColumnType,
  CreatedAt,
  ManyToMany,
  PrimaryKey,
  Table,
  UpdatedAt
} from "@ibestservices/ibest-orm";
import { Teacher } from "./Teacher";
import { Student } from "./Student";

@Table()
export class Course implements BaseEntity {
  @PrimaryKey()
  id?: number;
  @Column()
  name?: string
  @CreatedAt()
  createdAt?: string;
  @UpdatedAt()
  updatedAt?: string;
  @Column({ name: "teacher_id", type: ColumnType.INTEGER })
  teacherId?: number
  @BelongsTo(() => Teacher,{ foreignKey: 'teacherId' })
  teacher?: Teacher
  @ManyToMany(() => Student,{
    through: 'student_course',
    throughForeignKey: 'course_id',
    throughOtherKey: 'student_id',
    cascade: [CascadeType.Delete]
  })
  students?: Student[]
}

EducationPage.ets

arkts 复制代码
import {
  createCascadeHandler, getORM, metadataStorage, ORM
} from '@ibestservices/ibest-orm'
import { Course } from '../DBModel/Education/Course'
import { Student } from '../DBModel/Education/Student'
import { Teacher } from '../DBModel/Education/Teacher'

@Entry
@ComponentV2
struct EducationPage {
  @Local MyORM: ORM = getORM()
  @Local TArray: Teacher[] = []
  @Local CArray: Course[] = []
  @Local TSelectIndex: number = 0

  aboutToAppear(): void {
    this.MyORM.migrate(Teacher)
    this.MyORM.migrate(Student)
    this.MyORM.migrate(Course)

    this.TArray = this.MyORM.query(Teacher).with('courses').find();
    this.CArray = this.MyORM.query(Course)
      .with('students')
      .find();
  }

  build() {
    Column({ space: 10 }) {
      Scroll() {
        Row({ space: 10 }) {
          Button("插入基础数据").onClick((event: ClickEvent) => {
            for (let index = 0; index < 50; index++) {
              const element = new Teacher();
              element.name = `老师 ${index + 1} `
              element.age = Math.floor(Math.random() * (50 - 20 + 1)) + 20
              this.TArray.push(element);
            }
            this.MyORM.insert(this.TArray);
            this.TArray.forEach((t, tIndex) => {
              const c1 = new Course();
              c1.name = `课程 ${tIndex} - 1`;
              c1.teacherId = t.id;
              const c2 = new Course();
              c2.name = `课程 ${tIndex + 1} - 1`;
              c2.teacherId = t.id;
              this.MyORM.insert([c1, c2]);
            })
            this.CArray = this.MyORM.query(Course).find();
            this.TArray = this.MyORM.query(Teacher).with('courses').find();
          })
          Button("删除选择的老师对象").onClick(() => {
            const deleteTeacher = this.TArray[this.TSelectIndex];
            this.MyORM.delete(deleteTeacher);
            this.TArray = this.MyORM.query(Teacher).with('courses').find()
          })
          Button("修改老师名字").onClick((event: ClickEvent) => {
            const saveTeacher = this.TArray[this.TSelectIndex];
            saveTeacher.name = "我是修改后的老师名字";
            this.MyORM.save(saveTeacher);
            this.TArray = this.MyORM.query(Teacher).with('courses').find();
          })
          Button("条件查询 - 对象风格条件").onClick((event: ClickEvent) => {
            //gt:大于 gte:大于等于 lt:小于 lte:小于等于 ne:不等于
            //示例:查询年龄在22岁到35岁的老师
            this.TArray = this.MyORM.query(Teacher)
              .with('courses')
              .where({ age: { lte: 30 } })
              .where({ age: { gte: 20 } })
              .find();
          })
          Button("条件查询 - IN查询").onClick((event: ClickEvent) => {
            //查询年龄为25和35的老师
            this.TArray = this.MyORM.query(Teacher)
              .with('courses')
              .whereIn('age', [25, 35])
              .find()
          })
          Button("条件查询 - BETWEEN 查询").onClick((event: ClickEvent) => {
            //查询年龄在25到35岁的老师,包含25和35岁
            this.TArray = this.MyORM.query(Teacher)
              .with('courses')
              .whereBetween('age', 25, 35)
              .find()
          })
          Button("条件查询 - LIKE 查询").onClick((event: ClickEvent) => {
            //查询年龄尾数为5的老师,%5:以5开头,%5%:包含5,5%:以5结尾
            this.TArray = this.MyORM.query(Teacher)
              .with('courses')
              .whereLike('age', '5%')
              .find()
          })
          Button("选择字段查询").onClick((event: ClickEvent) => {
            //只查询老师名字
            this.TArray = this.MyORM.query(Teacher)
              .with('courses')
              .select('name')
              .find()
          })
          Button("查询排序").onClick((event: ClickEvent) => {
            //老师按年龄排序,asc:升序;desc:降序
            this.TArray = this.MyORM.query(Teacher)
              .with('courses')
              .orderBy('age', 'asc')
              .find()
          })
          Button("分页查询").onClick((event: ClickEvent) => {
            // 分页查询,查询第二页的内容,每页10个老师
            const page = 2;
            const pageSize = 10;
            this.TArray = this.MyORM.query(Teacher)
              .with('courses')
              .limit(pageSize)
              .offset((page - 1) * pageSize)
              .find();
          })
          Button("删除所有数据").onClick(() => {
            this.TArray.forEach((t, index) => {
              this.MyORM.deleteById(Teacher, t.id);
            })
            this.MyORM.query(Student).delete()
            this.TArray = this.MyORM.query(Teacher).with('courses').find();
            this.CArray = this.MyORM.query(Course)
              .find();
          })
        }
      }
      .scrollable(ScrollDirection.Horizontal)
      .height(80)
      .width("100%")

      Row() {
        Text("老师名字")
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
        Text("老师年龄")
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
        Text("授课名字集合")
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
      }
      .width("100%")
      .height(50)

      List() {
        ForEach(this.TArray, (t: Teacher, teacherIndex: number) => {
          ListItem() {
            Row() {
              Text(t.name)
                .layoutWeight(1)
              Text(t.age?.toString())
                .layoutWeight(1)
              Text(t.courses?.map((c: Course, cIndex: Number) => c.name).join(','))
                .layoutWeight(1)
            }
            .width("100%")
            .height("100%")
            .backgroundColor(teacherIndex == this.TSelectIndex ? Color.Yellow : Color.White)
          }
          .width("100%")
          .height(50)
          .onClick(() => {
            this.TSelectIndex = teacherIndex;
          })
        })
      }
      .width("100%")
      .layoutWeight(1)

      Row() {
        Text("课程名称")
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
        Text("学生数量")
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
      }
      .width("100%")
      .height(50)

      List() {
        ForEach(this.CArray, (t: Course, cIndex: Number) => {
          ListItem() {
            Row() {
              Text(t.name)
                .layoutWeight(1)
              Text(t.students?.map((c: Student, cIndex: Number) => c.name).join(','))
                .layoutWeight(1)
            }
            .width("100%")
            .height("100%")
          }
          .width("100%")
          .height(50)
        })
      }
      .width("100%")
      .layoutWeight(1)
    }
    .height('100%')
    .width('100%')
  }
}

总结

解放开发者双手,不需要费大量心思去写sql语句,提供较多的方法供开发者使用。

IBest-ORM官网链接:https://ibest-orm.ibestservices.com/

相关推荐
行者9617 小时前
Flutter鸿蒙跨平台开发:实现高性能可拖拽排序列表组件
flutter·harmonyos·鸿蒙
baobao熊17 小时前
【Harmony OS 6】地图操作系列-路程规划
华为·harmonyos
行者9617 小时前
Flutter FloatingActionButton在OpenHarmony平台的深度适配与优化实践
flutter·harmonyos·鸿蒙
儿歌八万首18 小时前
鸿蒙自定义相机开发:Camera Kit
数码相机·华为·harmonyos·harmonyos app
行者9618 小时前
Flutter跨平台开发:OpenHarmony平台卡片翻转组件的优化实践
flutter·harmonyos·鸿蒙
小雨青年19 小时前
鸿蒙 HarmonyOS 6 | ArkUI (06):表单交互 TextInput、Toggle、Slider 与 Picker 选择器
华为·交互·harmonyos
行者9619 小时前
Flutter适配OpenHarmony:手势交互的深度优化与实战应用
flutter·交互·harmonyos·鸿蒙
行者9619 小时前
Flutter与OpenHarmony深度融合:跨平台日历组件性能优化与适配实践
flutter·harmonyos·鸿蒙
行者9619 小时前
Flutter适配鸿蒙:SnackBar组件实践与优化策略
flutter·harmonyos·鸿蒙