跳至正文
Wener Notes

mikro-orm

约 3 分钟阅读
    • MIT, Typescript
    • ORM
    • 支持 Data Mapper, Unit of Work, Identity Map
    • 支持 MongoDB, MySQL, MariaDB, PostgreSQL, SQLite
    • 支持 dataloader
  • @mikro-orm/knex
    • Builder
  • mikro-orm 使用 knex, knex pool 默认 min:2, max:10
  • 不支持复杂多样的 JOIN 逻辑
  • 默认 cascade 为 persist
    • 新 entity 总会 persist - 忽略 cascade
TypeScript
// 让 user 和 userId 同时存在
@Property({ persist: false })
get userId(): string {
  return this.user.id;
}
TypeScript
const filters = em.applyFilters('EntityName', {}, {}, 'read');
qb.andWhere(filters);
bash
# mongodb, mysql, mariadb, sqlite
npm add @mikro-orm/core @mikro-orm/postgresql
依赖包
tsconfig.json
{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true,
  "esModuleInterop": true
}
TypeScript
import type { PostgreSqlDriver } from '@mikro-orm/postgresql'; // or any other driver package
import { EntityManager } from '@mikro-orm/postgresql';

const orm = await MikroORM.init<PostgreSqlDriver>({
  // 自动发现 - 不推荐
  // entities: ['./dist/entities'], // path to our JS entities (dist), relative to `baseDir`
  // entitiesTs: ['./src/entities'], // path to our TS entities (src), relative to `baseDir`
  // entities: ['./dist/app/**/entities'],
  // entitiesTs: ['./src/app/**/entities'],
  // entities: ['./dist/app/**/entities/*.entity.js'],
  // entitiesTs: ['./src/app/**/entities/*.entity.ts'],

  // 手动配置
  entities: [Author, Book, BookTag, Publisher, Test],
  discovery: { disableDynamicFileAccess: true },

  dbName: 'my-db-name',
  type: 'postgresql',
});
const em = orm.em as EntityManager;
bash
npm add -D @mikro-orm/cli
# mikro-orm.config.js
npx mikro-orm

mikro-orm generate-entities

# by dlx
pnpm --package=@mikro-orm/cli dlx mikro-orm

NODE_OPTIONS='--loader tsx' pnpm mikro-orm generate-entities --schema public -p ./src/server/db/entities/ -d
依赖包
  • mikro-orm.config.ts
    • 依赖 ts-node
TypeScript
export type MinimalOptionalEntityFields =
  | 'id'
  | 'uid'
  | 'createdAt'
  | 'updatedAt'
  | 'sid'
  | 'tid'
  | 'attributes'
  | 'properties'
  | 'extensions';

@Entity({ abstract: true })
export abstract class MinimalBaseEntity<Entity extends MinimalBaseEntity<any>> extends BaseEntity<Entity, 'id'> {
  // 在子类中定义 - Base 里的定义无法覆盖
  [OptionalProps]?: MinimalOptionalEntityFields;

  @PrimaryKey({ type: 'text', defaultRaw: 'public.gen_ulid()' })
  id!: string;

  //@PrimaryKey({ type: 'uuid', defaultRaw: 'uuid_generate_v4()' })
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
  uid!: string;

  @Property()
  createdAt: Date = new Date();

  @Property({ type: 'timestamptz', defaultRaw: 'current_timestamp' })
  createdAt!: Date;

  @Property({ onUpdate: () => new Date() })
  updatedAt: Date = new Date();
}

STI

  • STI -> Single Table Inheritance 单表继承
TypeScript
@Entity({
  discriminatorColumn: 'type',
  // discriminatorMap: { person: 'Person', employee: 'Employee' },
  // discriminatorValue: 'person',
  abstract: true, // discriminatorMap 不包含自己
})
export class BasePerson {
  @Enum()
  type!: 'person' | 'employee';
}
@Entity({
  discriminatorValue: 'person',
})
export class Person extends BasePerson {
  // ...
}
@Entity({
  discriminatorValue: 'employee',
})
export class Employee extends BasePerson {}

Collection

TypeScript
interface Collection<T> {
  load(): Promise<this>;
  loadItems(): Promise<T[]>;
  loadCount(): Promise<number>;

  matching(cond: FilterQuery<T>): this;

  getItems(): T[];

  add(...items: T[]): this;
  remove(...items: T[]): this;
  contains(item: T): boolean;
  count(): number;

  // 数组
  slice(start?: number, end?: number): T[];
  exists(cb: (item: T) => boolean): boolean;
  find(cb: (item: T, index: number) => boolean): T | undefined;
  filter(cb: (item: T, index: number) => boolean): T[];
  map<R>(mapper: (item: T, index: number) => R): R[];
  indexBy<K extends keyof T>(prop: K): Record<string, T>;

  shouldPopulate(populated?: boolean): boolean;
  populated(populated?: boolean): boolean;
  init(): Promise<this>;

  toJSON(): any;
}

RequestContext

  • Middleware RequestContext.create(orm.em, next)

Relationships

Metadata

  • MetadataStorage
    • Key ->EntityMetadata
    • key = entity && path ? entity + '-' + Utils.hash(path) : null
  • Utils.lookupPathFromDecorator
    • 获取 Decorator 调用的 path
TypeScript
// true
DemoEntity.prototype.__entity;
// boolean
DemoEntity.prototype.__baseEntity;
// EntityMetadata
DemoEntity.prototype.__meta;
DemoEntity.prototype.__config;
DemoEntity.prototype.__platform;
DemoEntity.prototype.__factory;
DemoEntity.prototype.__helper;
TypeScript
MetadataStorage.getMetadataFromDecorator(UserEntity);
TypeScript
// @Entity
export function Entity(options: EntityOptions<any> = {}) {
  return function <T>(target: T & Dictionary) {
    const meta = MetadataStorage.getMetadataFromDecorator(target);
    Utils.mergeConfig(meta, options);
    meta.class = target as unknown as Constructor<T>;

    if (!options.abstract || meta.discriminatorColumn) {
      meta.name = target.name;
    }

    return target;
  };
}

Entity

  • 好处
    • 使用 class 定义 Entity
    • 方便 setter/getter 等辅助方法
    • 与其他 OOP 语言语法类似
  • 坏处
    • 不方便 mixin 和 reuse 部分定义

EntitySchema

  • 好处
    • 可以动态定义
    • 可以复用字段
    • mixin
  • 坏处
    • 需要重复定义一个 类型
TypeScript
const schema = new EntitySchema({
  tableName: 'myNewTable',
  name: 'myNewTable',
  properties: {
    id: { primary: true, type: 'string' },
    price: { type: 'number', nullable: true, columnType: 'float' },
  },
});
orm.discoverEntity(schema);
em.create('myNewTable', { id: 'abc-123', price: 1.5 });
await em.flush();

defineEntity

  • v6.5+
  • 基于 EntitySchema, 但同时定义 类型
  • 使用类似 zod 的 DSL
  • Virtual Propertie 还是会用到 class

Migration

  • MigrationRunner
  • umzug
  • Migration
    • 调用 up/down
    • 调用 getQueries 获取生成的查询
bash
pnpm add -D @mikro-orm/cli
npx mikro-orm migration:create --initial

pnpm --package=@mikro-orm/cli dlx mikro-orm migration:create --initial
依赖包

ObjectHydrator

  • DB -> ORM
  • 动态编译的 Code

关联信息

反向链接、本文链接的其他页面和外部资料。

参考资料

GitHub5 条
其他外链3 条
最近更新commit 1990e6b编辑