문제 발생
한 api가 transaction
을 통해 데이터를 갱신하거나 생성하는 api일 때, 코드에서
const queryRunner = await getConnection().createQueryRunner();
await queryRunner.startTransaction();
위와 같이 쿼리 빌더를 통해 트랜잭션을 시작하고 각종 repository
에서 함수를 가져와 실행하였다.
오늘도 api를 열심히 수정하고 postman을 통해 호출하는데
호출이 계속 지연되면서 결국 위와 같이 status code가 500
이 뜨는 상황이 발생했다.
문제 해결
분명 로직은 잘못된게 없을텐데 하고 콘솔을 봤더니 처음에 트랜잭션이 실행되고 콘솔창에 찍히는 "START TRANSACTION"
이후에 또 "START TRANSACTION"
이 찍히는 것이었다.
그래서 트랜잭션이 일어나는 함수를 자세히 살펴보았더니..
// sampleService
async updateSample(...): Promise<void> {
const queryRunner = await getConnection().createQueryRunner();
await queryRunner.startTransaction();
try {
await this.sample2Repository.saveSample2({
...
});
await this.sample3Repository.saveSample3(queryRunner.manager, {
...
});
await queryRunner.commitTransaction();
} catch (e) {
await queryRunner.rollbackTransaction();
throw e;
} finally {
await queryRunner.release();
}
}
// sample2Repository
async saveSample2(...): Promise<void> {
const data = this.create(...);
await this.save(data, { reload: false });
}
// sample3Repository
async saveSample3(
transactionManager: EntityManager,
...
): Promise<void> {
if (!transactionManager) throw new Error();
const data = this.create();
...
await transactionManager.save(Sample3, data);
}
saveSample2
함수와 saveSample3
함수의 차이가 보이는가? 🧐
saveSample3 함수는 updateSample
함수에서 생성한 queryRunner를 인자로 받아 transactionManager.save()
로 DB에 데이터를 저장하고 있지만, saveSample2 함수는 그냥 함수 자체에서 this.save()
를 통해 저장하고 있다.
그렇게 되면 updateSample 에서 트랜잭션을 시작해도 saveSample2 까지 트랜잭션이 이어지지가 않고 saveSample2 에서 트랜잭션이 새로 시작
되어 오류가 발생하는 것이었다.
따라서, 한 함수에서 트랜잭션을 시작하고 나면 그 함수가 끝날 때까지 그 안에서 호출되는 (db를 건드리게 되는)함수들은 모두 이미 생성된 EntityManager(transactionManager)
를 인자로 받아 save
해야 한다.(delete
, update
도 마찬가지일 듯 하다)