NestJS + TypeORM(0.3x) 自定义仓库事务使用问题及替代方案咨询
问题
我在NestJS中通过以下方式创建自定义仓库:
iuser.repository.ts
import { UserEntity } from "../entity/user-entity"; export interface IUserRepository { findByEmail(email: string): Promise<UserEntity> }
user.repository.ts
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { UserEntity } from "src/user/domain/entity/user-entity"; import { IUserRepository } from "src/user/domain/repository/iuser.repository"; import { Repository } from "typeorm"; @Injectable() export class UserRepository extends Repository<UserEntity> implements IUserRepository { constructor( @InjectRepository(UserEntity) private readonly userRepository: Repository<UserEntity> ) { super(UserEntity, userRepository.manager) } async findByEmail(email: string): Promise<UserEntity> { return await this.userRepository .createQueryBuilder() .where('email = :email', { email }) .getOne(); } }
在服务中使用事务时,调用queryRunner.manager.withRepository(this.userRepository)编译无报错,但运行时抛出以下错误:
if (this.isReleased) throw new QueryRunnerAlreadyReleasedError() ^ QueryRunnerAlreadyReleasedError: Query runner already released. Cannot run queries anymore.
日志显示通过withRepository生成的新仓库实例的manager和queryRunner均为undefined。我知道withRepository应该生成绑定当前事务manager的自定义仓库实例,但不知道怎么正确使用。请问除了TypeORM文档里的getRepository().extend方法外,怎么在事务中用我这种方式实现的自定义仓库?
解决方案
你的自定义仓库实现存在两个核心问题:一是继承Repository时冗余注入了基础仓库实例,二是自定义方法直接依赖原始注入的仓库而非当前实例的manager。调整如下:
1. 重构自定义仓库,修正初始化逻辑
修改user.repository.ts,完整传递父类初始化参数,并让自定义方法使用当前实例的上下文:
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { UserEntity } from "src/user/domain/entity/user-entity"; import { IUserRepository } from "src/user/domain/repository/iuser.repository"; import { Repository } from "typeorm"; @Injectable() export class UserRepository extends Repository<UserEntity> implements IUserRepository { constructor( @InjectRepository(UserEntity) repository: Repository<UserEntity> ) { // 完整传递target、manager、queryRunner,保证实例上下文可被正确替换 super(repository.target, repository.manager, repository.queryRunner); } async findByEmail(email: string): Promise<UserEntity> { // 使用当前实例的createQueryBuilder,确保绑定正确的事务上下文 return this.createQueryBuilder() .where('email = :email', { email }) .getOne(); } }
2. 事务中正确获取绑定事务的仓库实例
在服务中使用事务时,改用queryRunner.manager.getCustomRepository获取绑定当前事务的自定义仓库:
import { Injectable } from '@nestjs/common'; import { UserRepository } from './user.repository'; import { getConnection } from 'typeorm'; @Injectable() export class UserService { constructor( private readonly userRepository: UserRepository ) {} async someTransactionalOperation() { const connection = getConnection(); const queryRunner = connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { // 获取绑定当前事务的自定义仓库实例 const txUserRepo = queryRunner.manager.getCustomRepository(UserRepository); // 执行事务内操作 const user = await txUserRepo.findByEmail('test@example.com'); // ...其他数据库操作 await queryRunner.commitTransaction(); return user; } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } } }
关键说明
- 父类初始化必须传递
target、manager、queryRunner三个参数,这样TypeORM才能在事务场景中正确替换实例的上下文。 - 自定义方法必须使用
this.createQueryBuilder(),而非依赖注入的原始仓库,确保操作绑定当前实例的manager/queryRunner。 - 事务场景下用
getCustomRepository替代withRepository,TypeORM会自动基于当前事务的manager生成新的自定义仓库实例,保证所有操作都在事务内执行。
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

