MikroORM

MikroORM 是一个 TypeScript ORM,基于 Data Mapper、Unit of Work 和 Identity Map 模式。它是 TypeORM 的一个优秀替代品,提供了更简洁的 API 和更好的性能。

信息 信息 MikroORM 是一个第三方包,不受 NestJS 核心团队管理。请在 MikroORM GitHub 仓库 中报告该库中的任何问题。

安装

首先,安装必要的依赖:

npm install --save mikro-orm/core mikro-orm/mysql # 或其他数据库驱动
npm install --save-dev mikro-orm/cli

集成到 NestJS

与 Nest 集成 MikroORM 的最简单方法是通过 @mikro-orm/nestjs 包:

npm install --save @mikro-orm/nestjs

然后,在根模块中配置 MikroORM:

import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { User } from './user.entity';

@Module({
  imports: [
    MikroOrmModule.forRoot({
      entities: [User],
      dbName: 'nestjs-mikroorm',
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      user: 'root',
      password: 'password',
    }),
    MikroOrmModule.forFeature([User]),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

实体定义

定义一个简单的 User 实体:

import { Entity, PrimaryKey, Property } from 'mikro-orm';

@Entity()
export class User {
  @PrimaryKey()
  id: number;

  @Property()
  name: string;

  @Property()
  email: string;

  @Property({ default: () => 'NOW()' })
  createdAt: Date = new Date();

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

仓库模式

MikroORM 支持仓库设计模式。对于每个实体,我们可以使用自动生成的仓库或创建自定义仓库。

使用自动生成的仓库:

import { Injectable } from '@nestjs/common';
import { EntityRepository } from '@mikro-orm/core';
import { InjectRepository } from '@mikro-orm/nestjs';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: EntityRepository<User>,
  ) {}

  async findAll(): Promise<User[]> {
    return this.userRepository.findAll();
  }

  async findOne(id: number): Promise<User | null> {
    return this.userRepository.findOne(id);
  }

  async create(user: Partial<User>): Promise<User> {
    const newUser = this.userRepository.create(user);
    await this.userRepository.persistAndFlush(newUser);
    return newUser;
  }

  async update(id: number, user: Partial<User>): Promise<User | null> {
    const existingUser = await this.userRepository.findOne(id);
    if (!existingUser) {
      return null;
    }
    Object.assign(existingUser, user);
    await this.userRepository.persistAndFlush(existingUser);
    return existingUser;
  }

  async delete(id: number): Promise<boolean> {
    const user = await this.userRepository.findOne(id);
    if (!user) {
      return false;
    }
    await this.userRepository.removeAndFlush(user);
    return true;
  }
}

自定义仓库

创建自定义仓库:

import { EntityRepository } from '@mikro-orm/core';
import { User } from './user.entity';

export class UserRepository extends EntityRepository<User> {
  async findByEmail(email: string): Promise<User | null> {
    return this.findOne({ email });
  }

  async findActiveUsers(): Promise<User[]> {
    return this.find({ active: true });
  }
}

在模块中注册自定义仓库:

import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { User } from './user.entity';
import { UserRepository } from './user.repository';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  imports: [
    MikroOrmModule.forFeature({
      entities: [User],
      repositories: [UserRepository],
    }),
  ],
  providers: [UserService],
  controllers: [UserController],
})
export class UserModule {}

然后在服务中使用自定义仓库:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@mikro-orm/nestjs';
import { User } from './user.entity';
import { UserRepository } from './user.repository';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: UserRepository,
  ) {}

  async findByEmail(email: string): Promise<User | null> {
    return this.userRepository.findByEmail(email);
  }

  async findActiveUsers(): Promise<User[]> {
    return this.userRepository.findActiveUsers();
  }
}

自动加载实体

手动将实体添加到连接选项的实体数组中可能很繁琐。你可以使用静态 glob 路径来自动加载实体:

MikroOrmModule.forRoot({
  entities: ['./dist/**/*.entity.js'],
  entitiesTs: ['./src/**/*.entity.ts'],
  // 其他配置...
});

注意,glob 路径在 webpack 中不受支持,所以如果你在 monorepo 中构建应用程序,你将无法使用它们。

迁移

MikroORM 提供了迁移功能来管理数据库模式的变更。首先,初始化迁移配置:

npx mikro-orm migration:init

然后,生成迁移:

npx mikro-orm migration:create

运行迁移:

npx mikro-orm migration:up

测试

在测试中使用 MikroORM:

import { Test, TestingModule } from '@nestjs/testing';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { UserService } from './user.service';
import { User } from './user.entity';

describe('UserService', () => {
  let service: UserService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [
        MikroOrmModule.forRoot({
          entities: [User],
          dbName: ':memory:',
          type: 'sqlite',
          allowGlobalContext: true,
        }),
        MikroOrmModule.forFeature([User]),
      ],
      providers: [UserService],
    }).compile();

    service = module.get<UserService>(UserService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('should create a user', async () => {
    const user = await service.create({ name: 'John', email: 'john@example.com' });
    expect(user).toBeDefined();
    expect(user.id).toBeDefined();
    expect(user.name).toBe('John');
    expect(user.email).toBe('john@example.com');
  });
});

总结

MikroORM 是一个功能强大的 ORM 库,与 NestJS 集成简单直接。它提供了仓库模式、迁移功能和自动实体加载等特性,使数据库操作更加便捷和类型安全。

要了解更多关于 MikroORM 的信息,请查看 官方文档