You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 22:07:01