### MikroORM
本秘籍旨在帮助用户在 Nest 中开始使用 MikroORM。 MikroORM 是基于数据映射器、工作单元和身份映射模式的 Node.js 的 TypeScript ORM。它是 TypeORM 的绝佳替代品,从 TypeORM 迁移应该相当容易。可以找到关于 MikroORM 的完整文档\[这里\](https://mikro-orm.io/docs).
> **信息**`@mikro-orm/nestjs`是第三方包,不由 NestJS 核心团队管理。报告任何问题请提交到这里[代码仓库](https://github.com/mikro-orm/nestjs).
#### 安装
将 MikroORM 集成到 Nest 的最简单方法是通过 \[`@mikro-orm/nestjs`module\](https://github.com/mikro-orm/nestjs)。只需将它安装在 Nest、MikroORM 和底层驱动程序旁边:
~~~bash
$ npm i @mikro-orm/core @mikro-orm/nestjs @mikro-orm/mysql # for mysql/mariadb
~~~
MikroORM 还支持`postgres`、`sqlite` 和`mongo`。查看所有驱动程序的\[官方文档\](https://mikro-orm.io/docs/usage-with-sql/)。
安装过程完成后,我们可以将`MikroOrmModule`导入到根`AppModule`中
~~~typescript
@Module({
imports: [
MikroOrmModule.forRoot({
entities: ['./dist/entities'],
entitiesTs: ['./src/entities'],
dbName: 'my-db-name.sqlite3',
type: 'sqlite',
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
~~~
`forRoot()` 方法接受与 MikroORM 包中的`init()` 相同的配置对象。检查\[此页面\](https://mikro-orm.io/docs/configuration)以获取完整的配置文档。
或者,我们可以\[配置 CLI\](https://mikro-orm.io/docs/installation#setting-up-the-commandline-tool) 通过创建配置文件`mikro-orm.config.ts`然后调用没有任何参数的`forRoot()`。当您使用使用`tree-shaking`的构建工具时,这将不起作用。
~~~typescript
@Module({
imports: [
MikroOrmModule.forRoot(),
],
...
})
export class AppModule {}
~~~
之后,`EntityManager` 将可用于在整个项目中注入(无需在其他地方导入任何模块)。
~~~ts
import { MikroORM } from '@mikro-orm/core';
// Import EntityManager from your driver package or `@mikro-orm/knex`
import { EntityManager } from '@mikro-orm/mysql';
@Injectable()
export class MyService {
constructor(
private readonly orm: MikroORM,
private readonly em: EntityManager,
) {}
}
~~~
> \*\*INFO\*\*注意`EntityManager`是从`@mikro-orm/driver`包中导入的,这里的驱动是`mysql`、`sqlite`、`postgres`或者你正在使用的驱动。如果你有`@mikro-orm/knex`作为依赖安装,你也可以从那里导入`EntityManager`。
####存储库
MikroORM 支持存储库设计模式。对于每个实体,我们都可以创建一个存储库。阅读关于存储库的完整文档\[这里\](https://mikro-orm.io/docs/repositories)。要定义应在当前范围内注册哪些存储库,您可以使用`forFeature()` 方法。例如,以这种方式:
> \*\*INFO\*\*您不应该\*\*通过`forFeature()`注册您的基础实体,因为这些实体没有存储库。另一方面,基本实体需要是`forRoot()`(或一般的ORM配置)列表的一部分。
~~~typescript
// photo.module.ts
@Module({
imports: [MikroOrmModule.forFeature([Photo])],
providers: [PhotoService],
controllers: [PhotoController],
})
export class PhotoModule {}
~~~
并将其导入到 root`AppModule` 中:
~~~typescript
// app.module.ts
@Module({
imports: [MikroOrmModule.forRoot(...), PhotoModule],
})
export class AppModule {}
~~~
通过这种方式,我们可以使用`@InjectRepository()`装饰器将`PhotoRepository`注入`PhotoService`:
~~~typescript
@Injectable()
export class PhotoService {
constructor(
@InjectRepository(Photo)
private readonly photoRepository: EntityRepository<Photo>,
) {}
}
~~~
#### 使用自定义存储库
当使用自定义存储库时,我们可以通过使用与`getRepositoryToken()`方法相同的方式命名我们的存储库来绕过对`@InjectRepository()`装饰器的需求:
~~~ts
export const getRepositoryToken = <T>(entity: EntityName<T>) =>
`${Utils.className(entity)}Repository`;
~~~
换句话说,只要我们将存储库命名为与实体被调用相同,附加`Repository`后缀,存储库将自动注册到Nest DI容器中。
~~~ts
// `**./author.entity.ts**`
@Entity()
export class Author {
// to allow inference in `em.getRepository()`
[EntityRepositoryType]?: AuthorRepository;
}
// `**./author.repository.ts**`
@Repository(Author)
export class AuthorRepository extends EntityRepository<Author> {
// your custom methods...
}
~~~
由于自定义存储库名称与`getRepositoryToken()`返回的名称相同,我们不再需要`@InjectRepository()`装饰器:
~~~ts
@Injectable()
export class MyService {
constructor(private readonly repo: AuthorRepository) {}
}
~~~
#### 自动加载实体
> \*\*INFO\*\*`autoLoadEntities`选项在 v4.1.0 中添加
手动将实体添加到连接选项的实体数组中可能很乏味。此外,从根模块引用实体会破坏应用程序域边界并导致将实现细节泄漏到应用程序的其他部分。为了解决这个问题,可以使用静态全局路径。
但是请注意,webpack 不支持 glob 路径,因此如果您在 monorepo 中构建应用程序,您将无法使用它们。为了解决这个问题,提供了一种替代解决方案。要自动加载实体,请将配置对象(传入`forRoot()`方法)的`autoLoadEntities`属性设置为`true`,如下图:
~~~ts
@Module({
imports: [
MikroOrmModule.forRoot({
...
autoLoadEntities: true,
}),
],
})
export class AppModule {}
~~~
指定该选项后,通过`forFeature()`方法注册的每个实体都将自动添加到配置对象的实体数组中。
> \*\*INFO\*\*请注意,未通过`forFeature()`方法注册但仅从实体引用(通过关系)的实体将不会通过`autoLoadEntities`设置包含在内。
> \*\*INFO\*\*Using`autoLoadEntities` 对 MikroORM CLI 也没有影响 - 因为我们仍然需要 CLI 配置和完整的实体列表。另一方面,我们可以在那里使用 glob,因为 CLI 不会通过 webpack。
#### 序列化
> \*\*注意\*\*MikroORM 将每个实体关系包装在`Reference`或`Collection`对象中,以提供更好的类型安全性。这将使 \[Nest 的内置序列化程序\](https://docs.nestjs.com/techniques/serialization) 对任何包装的关系视而不见。换句话说,如果您从 HTTP 或 WebSocket 处理程序返回 MikroORM 实体,它们的所有关系都不会被序列化。
幸运的是,MikroORM 提供了一个\[序列化 API\](https://mikro-orm.io/docs/serializing),可以用来代替`ClassSerializerInterceptor`。
~~~typescript
@Entity()
export class Book {
@Property({ hidden: true }) // Equivalent of class-transformer's `@Exclude`
hiddenField = Date.now();
@Property({ persist: false }) // Similar to class-transformer's `@Expose()`. Will only exist in memory, and will be serialized.
count?: number;
@ManyToOne({ serializer: value => value.name, serializedName: 'authorName' }) // Equivalent of class-transformer's `@Transform()`
author: Author;
}
~~~
#### 请求队列中的作用域处理程序
> \*\*INFO\*\*`@UseRequestContext()`装饰器在 v4.1.0 中被添加
正如 \[docs\](https://mikro-orm.io/docs/identity-map) 中所述,我们需要为每个请求提供一个干净的状态。由于通过中间件注册的`RequestContext`帮助程序,这会自动处理。
但是中间件只针对常规的 HTTP 请求句柄执行,如果我们需要一个请求范围之外的方法怎么办?其中一个示例是队列处理程序或计划任务。
我们可以使用`@UseRequestContext()`装饰器。它要求您首先将`MikroORM`实例注入当前上下文,然后它将用于为您创建上下文。在幕后,装饰器将为您的方法注册新的请求上下文并在上下文中执行它。
~~~ts
@Injectable()
export class MyService {
constructor(private readonly orm: MikroORM) {}
@UseRequestContext()
async doSomething() {
// this will be executed in a separate context
}
}
~~~
####将`AsyncLocalStorage`用于请求上下文
默认情况下,`domain`api 在`RequestContext`helper 中使用。由于`@mikro-orm/core@4.0.3`,如果您使用最新的节点版本,您也可以使用新的`AsyncLocalStorage`:
~~~typescript
// create new (global) storage instance
const storage = new AsyncLocalStorage<EntityManager>();
@Module({
imports: [
MikroOrmModule.forRoot({
// ...
registerRequestContext: false, // disable automatatic middleware
context: () => storage.getStore(), // use our AsyncLocalStorage instance
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
// register the request context middleware
const app = await NestFactory.create(AppModule, { ... });
const orm = app.get(MikroORM);
app.use((req, res, next) => {
storage.run(orm.em.fork(true, true), next);
});
~~~
#### 测试
`@mikro-orm/nestjs` 包公开了`getRepositoryToken()` 函数,该函数根据给定实体返回准备好的令牌以允许模拟存储库。
~~~typescript
@Module({
providers: [
PhotoService,
{
provide: getRepositoryToken(Photo),
useValue: mockedRepository,
},
],
})
export class PhotoModule {}
~~~
#### 示例
可以在 \[这里\](https://github.com/mikro-orm/nestjs-realworld-example-app) 找到带有 MikroORM 的 NestJS 的真实示例
- 介绍
- 概述
- 第一步
- 控制器
- 提供者
- 模块
- 中间件
- 异常过滤器
- 管道
- 守卫
- 拦截器
- 自定义装饰器
- 基础知识
- 自定义提供者
- 异步提供者
- 动态模块
- 注入作用域
- 循环依赖
- 模块参考
- 懒加载模块
- 应用上下文
- 生命周期事件
- 跨平台
- 测试
- 技术
- 数据库
- Mongo
- 配置
- 验证
- 缓存
- 序列化
- 版本控制
- 定时任务
- 队列
- 日志
- Cookies
- 事件
- 压缩
- 文件上传
- 流式处理文件
- HTTP模块
- Session(会话)
- MVC
- 性能(Fastify)
- 服务器端事件发送
- 安全
- 认证(Authentication)
- 授权(Authorization)
- 加密和散列
- Helmet
- CORS(跨域请求)
- CSRF保护
- 限速
- GraphQL
- 快速开始
- 解析器(resolvers)
- 变更(Mutations)
- 订阅(Subscriptions)
- 标量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 联合类型
- 枚举(Enums)
- 字段中间件
- 映射类型
- 插件
- 复杂性
- 扩展
- CLI插件
- 生成SDL
- 其他功能
- 联合服务
- 迁移指南
- Websocket
- 网关
- 异常过滤器
- 管道
- 守卫
- 拦截器
- 适配器
- 微服务
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定义传输器
- 异常过滤器
- 管道
- 守卫
- 拦截器
- 独立应用
- Cli
- 概述
- 工作空间
- 库
- 用法
- 脚本
- Openapi
- 介绍
- 类型和参数
- 操作
- 安全
- 映射类型
- 装饰器
- CLI插件
- 其他特性
- 迁移指南
- 秘籍
- CRUD 生成器
- 热重载
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模块
- Swagger
- 健康检查
- CQRS
- 文档
- Prisma
- 静态服务
- Nest Commander
- 问答
- Serverless
- HTTP 适配器
- 全局路由前缀
- 混合应用
- HTTPS 和多服务器
- 请求生命周期
- 常见错误
- 实例
- 迁移指南
- 发现
- 谁在使用Nest?