它是 TypeORM 的绝佳替代品,从 TypeORM 迁移应该相当容易。有关 MikroORM 的完整文档可在 此处 找到。
@mikro-orm/nestjs
是第三方软件包,不受 NestJS 核心团队管理。请在 适当的存储库 中报告发现的与库相关的任何问题。
安装
将 MikroORM 集成到 Nest 的最简单方法是通过 @mikro-orm/nestjs
模块。
只需将其安装在 Nest、MikroORM 和底层驱动程序旁边即可:
$ npm i @mikro-orm/core @mikro-orm/nestjs @mikro-orm/sqlite
MikroORM 还支持 postgres
、sqlite
和 mongo
。请参阅官方文档了解所有驱动程序。
安装过程完成后,我们可以将MikroOrmModule
导入到根AppModule
中。
import { SqliteDriver } from '@mikro-orm/sqlite'
@Module({
imports: [
MikroOrmModule.forRoot({
entities: ['./dist/entities'],
entitiesTs: ['./src/entities'],
dbName: 'my-db-name.sqlite3',
driver: SqliteDriver,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
}
forRoot()
方法接受与 MikroORM 包中的 init()
相同的配置对象。查看 此页面 以获取完整的配置文档。
或者,我们可以通过创建配置文件 mikro-orm.config.ts
来 配置 CLI,然后调用不带任何参数的 forRoot()
。
@Module({
imports: [
MikroOrmModule.forRoot(),
],
...
})
export class AppModule {}
但是当你使用采用树摇的构建工具时,这将不起作用,因此最好明确提供配置:
import config from './mikro-orm.config'; // your ORM config
@Module({
imports: [
MikroOrmModule.forRoot(config),
],
...
})
export class AppModule {}
之后,EntityManager
将可供注入整个项目(无需在其他地方导入任何模块)。
// Import everytyhing from your driver package or `@mikro-orm/knex`
import { EntityManager, MikroORM } from '@mikro-orm/sqlite'
@Injectable()
export class MyService {
constructor(
private readonly orm: MikroORM,
private readonly em: EntityManager,
) {}
}
请注意,EntityManager
是从 @mikro-orm/driver
包导入的,其中驱动程序是 mysql
、sqlite
、postgres
或您正在使用的驱动程序。如果您已将 @mikro-orm/knex
安装为依赖项,您也可以从那里导入 EntityManager
。
存储库
MikroORM 支持存储库设计模式。对于每个实体,我们都可以创建一个存储库。阅读有关存储库的完整文档 此处。要定义哪些存储库应在当前范围内注册,您可以使用 forFeature()
方法。例如,以这种方式:
您不应该通过 forFeature()
注册您的基础实体,因为没有这些实体的存储库。另一方面,基础实体需要成为 forRoot()
中列表的一部分(或一般在 ORM 配置中)。
// photo.module.ts
@Module({
imports: [MikroOrmModule.forFeature([Photo])],
providers: [PhotoService],
controllers: [PhotoController],
})
export class PhotoModule {}
并将其导入到根 AppModule
:
// app.module.ts
@Module({
imports: [MikroOrmModule.forRoot(...), PhotoModule],
})
export class AppModule {}
这样我们就可以使用 @InjectRepository()
装饰器将 PhotoRepository
注入到 PhotoService
:
@Injectable()
export class PhotoService {
constructor(
@InjectRepository(Photo)
private readonly photoRepository: EntityRepository<Photo>,
) {}
}
使用自定义存储库
使用自定义存储库时,我们不再需要 @InjectRepository()
装饰器,因为 Nest DI 根据类引用进行解析。
// `**./author.entity.ts**`
@Entity({ repository: () => AuthorRepository })
export class Author {
// to allow inference in `em.getRepository()`
[EntityRepositoryType]?: AuthorRepository
}
// `**./author.repository.ts**`
export class AuthorRepository extends EntityRepository<Author> {
// your custom methods...
}
As the custom repository name is the same as what getRepositoryToken()
would return, we do not need the @InjectRepository()
decorator anymore:
@Injectable()
export class MyService {
constructor(private readonly repo: AuthorRepository) {}
}
自动加载实体
手动将实体添加到连接选项的实体数组中可能非常繁琐。此外,从根模块引用实体会破坏应用程序域边界,并导致实现细节泄露到应用程序的其他部分。为了解决这个问题,可以使用静态 glob 路径。
但请注意,webpack 不支持 glob 路径,因此如果您在 monorepo 中构建应用程序,则无法使用它们。为了解决这个问题,我们提供了另一种解决方案。要自动加载实体,请将配置对象(传递到 forRoot()
方法)的 autoLoadEntities
属性设置为 true
,如下所示:
@Module({
imports: [
MikroOrmModule.forRoot({
...
autoLoadEntities: true,
}),
],
})
export class AppModule {}
指定该选项后,通过 forFeature()
方法注册的每个实体都将自动添加到配置对象的实体数组中。
请注意,未通过 forFeature()
方法注册但仅从实体(通过关系)引用的实体不会通过 autoLoadEntities
设置包含在内。
使用 autoLoadEntities
对 MikroORM CLI 也没有影响 - 为此我们仍然需要包含完整实体列表的 CLI 配置。另一方面,我们可以在那里使用 glob,因为 CLI 不会通过 webpack。
序列化
MikroORM 将每个实体关系包装在 Reference<T>
或 Collection<T>
对象中,以提供更好的类型安全性。这将使 Nest 的内置序列化器 对任何包装的关系视而不见。换句话说,如果您从 HTTP 或 WebSocket 处理程序返回 MikroORM 实体,则它们的所有关系都不会被序列化。
幸运的是,MikroORM 提供了一个 序列化 API,可以代替 ClassSerializerInterceptor
使用。
@Entity()
export class Book {
@Property({ hidden: true }) // 相当于 class-transformer 的 `@Exclude`
hiddenField = Date.now()
@Property({ persist: false }) // 类似于 class-transformer 的 `@Expose()`。只会存在于内存中,并且会被序列化。
count?: number
@ManyToOne({
serializer: value => value.name,
serializedName: 'authorName',
}) // 相当于 class-transformer 的 `@Transform()`
author: Author
}
队列中的请求范围处理程序
如 docs 中所述,我们需要每个请求都有一个干净的状态。这可以通过通过中间件注册的 RequestContext
帮助程序自动处理。
但中间件仅针对常规 HTTP 请求处理程序执行,如果我们需要除此之外的请求范围方法怎么办?队列处理程序或计划任务就是一个例子。
我们可以使用 @CreateRequestContext()
装饰器。它要求您首先将 MikroORM
实例注入当前上下文,然后它将用于为您创建上下文。在后台,装饰器将为您的方法注册新的请求上下文并在上下文中执行它。
@Injectable()
export class MyService {
constructor(private readonly orm: MikroORM) {}
@CreateRequestContext()
async doSomething() {
// this will be executed in a separate context
}
}
顾名思义,此装饰器始终创建新上下文,而其替代方法 @EnsureRequestContext
仅在其不在另一个上下文中时才创建它。
测试
@mikro-orm/nestjs
包公开了 getRepositoryToken()
函数,该函数根据给定实体返回准备好的令牌,以允许模拟存储库。
@Module({
providers: [
PhotoService,
{
// 或者当您有自定义存储库时:`provide: PhotoRepository`
provide: getRepositoryToken(Photo),
useValue: mockedRepository,
},
],
})
export class PhotoModule {}
Example
可以在此处 找到 NestJS 与 MikroORM 的真实示例