### 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?