如何在NestJS中正确使用MirkoORM自定义仓库?
NestJS 中使用 MikroORM 的常见问题解答
问题与解答
1. 通过MikroORM.forFeature()注册实体时会自动注册仓库,注入的自定义仓库(如RefreshTokenRepository)是否会包含EntityManager(EM)的新实例?
注入的自定义仓库不会持有独立的EM新实例。在NestJS集成场景下,仓库会自动关联到当前请求上下文绑定的EM实例(由RequestContext管理)。每次请求都会创建新的请求隔离EM,仓库会复用这个上下文EM,而非自行创建新实例。
2. 使用this.orm.em和this.refreshTokenRepository.getEntityManager()获取EM有什么区别?
this.orm.em获取的是全局默认EM实例,若未配置RequestContext,该实例是全局共享的,多请求并发时存在数据污染风险,不适合业务操作。this.refreshTokenRepository.getEntityManager()获取的是当前请求上下文绑定的EM实例,也就是RequestContext中创建的请求隔离EM,安全且符合多请求并发场景的要求。
在正确配置RequestContext中间件后,仓库的getEntityManager()会自动指向当前请求的EM,而直接调用orm.em可能拿到全局实例,这是两者的核心区别。
3. 若需要注入MikroORM来获取新的EM实例,那么注入自定义仓库的意义是什么?
自定义仓库的核心价值体现在以下几点:
- 业务逻辑封装:将实体相关的复杂查询、业务操作封装在仓库中(比如示例中的
permissionsOfRole方法),避免在服务层重复编写ORM查询代码,提升代码复用性。 - 类型安全保障:自定义仓库可以返回强类型的实体与结果,比直接使用EM的泛型方法更直观,减少类型错误。
- 解耦业务与ORM细节:服务层只需调用仓库提供的方法,无需关心底层ORM的查询语法,后续更换ORM或调整查询逻辑时,仅需修改仓库实现。
- 自动关联上下文EM:仓库会自动绑定当前请求的EM,无需手动管理EM实例的上下文关联,比每次从
orm获取EM再手动处理更简洁高效。
4. 已添加RequestContext中间件,但仓库仍出现集合被之前方法填充的问题:JWT策略中查询Role并填充permissions后,后续获取User时(User的roles为预加载),Role的permissions集合已被填充,但该集合配置为非预加载,原因是什么?
这是MikroORM的一级缓存(Identity Map)机制导致的:同一个请求上下文内,EM会缓存已加载的实体实例。当你第一次查询Role并填充permissions后,该Role实例会被存入EM的缓存。后续查询User时,由于User的roles配置为预加载(eager: true),ORM会直接从缓存中取出已有的Role实例,而非重新查询数据库,因此之前填充的permissions集合会被保留,即使Role实体的permissions配置为eager: false。
解决方法:
- 强制绕过缓存,重新从数据库加载Role:在查询User时添加
refresh: true选项const user = await this.userRepository.findOne({ id }, { populate: ['roles'], refresh: true }); - 手动从EM中移除已缓存的Role实例(仅在明确需要时使用):
this.roleRepository.getEntityManager().detach(role);
相关代码示例
RefreshToken 实体
@Entity({ tableName: 'refresh_token', customRepository: () => RefreshTokenRepository }) export default class RefreshToken extends BaseEntity<RefreshToken, 'id'> { [EntityRepositoryType]?: RefreshTokenRepository @PrimaryKey({ type: 'uuid', nullable: false, autoincrement: false, unique: true, primary: true }) id!: string }
AuthenticationService 服务
@Injectable() export default class AuthenticationService { private logger = new Logger(AuthenticationService.name) constructor( private readonly orm: MikroORM, private readonly jwtService: JwtService, private readonly passwordVerifier: PasswordVerifier, private readonly userRepository: UserRepository, private readonly refreshTokenRepository: RefreshTokenRepository ) {} @CreateRequestContext() private async passwordGrantType( payload: IssueTokenPayload ): Promise<Either<AccessDeniedError | UnexpectedError, TokenResponse>> { const em = this.orm.em } }
main.ts 配置
;(async function () { const app = await NestFactory.create(AppModule, { bufferLogs: true }) const orm = app.get(MikroORM) const loggerService = app.get(AppBindings.Logger) app.use((req, res, next) => { RequestContext.create(orm.em, next) }) })();
RoleRepository 方法
async permissionsOfRole(roleId: string): Promise<Permission[]> { const role = await this.findOne({ id: roleId }, { populate: ['permissions'] }) if (!role) return null return role.permissions.getItems() }
User 实体
export default class User extends AggregateRoot < User, 'id' > { [EntityRepositoryType] ? : UserRepository // More code here @ManyToMany({ eager: true, entity: () => Role, pivotEntity: () => UserRole, joinColumn: 'user_id', inverseJoinColumn: 'role_id' }) roles ? = new Collection < Role > (this) }
Role 实体
export default class Role extends AggregateRoot < Role, 'id' > { [EntityRepositoryType] ? : RoleRepository @Property({ type: 'character', length: 50, nullable: false }) name!: string @ManyToMany({ eager: false, entity: () => Permission, pivotEntity: () => RolePermission, joinColumn: 'role_id', inverseJoinColumn: 'permission_id' }) permissions = new Collection < Permission > (this) }
内容的提问来源于stack exchange,提问作者z0mbieKale
相关产品推荐
相关产品推荐

