Contents

typeORM transaction에서 repository 사용하기

<사진: unsplash>

transaction 구성중 Entity가 아닌 Repository 사용시

typeORM에서 transaction을 사용하는 방법은 몇가지가 있지만,
복잡한 service단의 처리에서는 아래의 queryRunner 방식이 더 적합할것 같아서
dataSource.createQueryRunner() 방식을 사용했다.1
 

// transaction으로 묶어주기  
const queryRunner = dataSource.createQueryRunner();  
await queryRunner.connect();  
await queryRunner.startTransaction();  
  
try {  
  // feed 저장  
  let newTempFeed: Feed = plainToInstance(FeedDto, feedInfo);  
  const tempFeed = await FeedRepository.createFeed(newTempFeed); //<- 문제!!
  // ... 추가 코드들
} catch (err) {  
  await queryRunner.rollbackTransaction();  
  throw new Error(`createTempFeed TRANSACTION error: ${err}`);  
} finally {  
  await queryRunner.release();  
}

 

문제 - commit 중복으로 rollbackTransaction 실패

FeedRepository는 이미 그 자체로 코드 실행시 transaction이 따로이 실행된다.
즉, 여기서 이미 commit이 이루어진다는 뜻!
 

따라서 rollbackTransaction을 하는데 있어 FeedRepository.createFeed 코드는 이미 commit이 되었기에 rollback 해당 대상에서 벗어나게 된다.
 
https://i.imgur.com/5WtPMAr.png  

위와 같이 FeedRepository에서 START TRANSACTION이 따로이 한번 더 일어나고,
아직 실행중이지만 도중에 COMMIT이 되어버림을 확인할 수 있다.
 

해결 - withRepository 메소드

고민을 해보자!

  1. transaction의 dataSource.manager는 따로 동작한다.
  2. 즉, 곰곰히 생각해보면 FeedRepository 단독실행이 아닌 아닌 저 transaction단에서 동작하는 범주 안에서 코딩이 이루어져야 한다.
  3. 2가지 방안이 떠올랐다. 첫번째는 FeedRepository안에 만들어놓은 메소드를 포기하고 따로이 다시금 transaction의 try문단 안에 코딩을 하는 것이고, 다른 하나는 저 FeedRepository를 어떻게든 transaction 동작 범주 안으로 우겨넣는 방법일 것이다.

 

다시금 코딩을 하자니 상당히 비효율적이고, 이건 도저히 아니라는 판단이 들었다.
그럼 우겨넣는 방법인데…
 

구글링을 해봐도 typeORM의 버전 업그레이드로 관련문서의 수도 적었고, 있다하더라도 대개 nestJS의 customRepository 관련 문서였다.
 

최후의 수단으로 IDE에서 사용가능한 메소드를 쭉 하나하나 살펴봤다.
그러다가 찾은 메소드 withRepository  
https://i.imgur.com/Smjr9j8.png  

Note
manager.withRepository
지정된 리포지토리에서 새 리포지토리 인스턴스를 생성하고 현재 EntityManager 인스턴스를 해당 리포지토리로 설정합니다. 트랜잭션에서 사용자 지정 리포지토리로 작업하는 데 사용됩니다.

 

아~주 적절하다!  

이렇게까지 복잡했던 이유는, 만약 해당 코드가 단순히 기본 Entity라면
 

const tempFeed = await queryRunner.manager.save(Feed, newTempFeed);

 

간단하게 뭐 이렇게 나가면 된다.
 

하지만 단순한 Entity가 아닌, 이미 일련의 실행과정을 코딩해놓은 Repository이기에 아래와 같이 withRepository 메소드를 사용한다.
 

그러면 해당 레포에서 static으로 박아놓은 .createFeed라는 하위 메소드도 연결해서 사용가능하다.
 

const tempFeed = await queryRunner.manager  
  .withRepository(FeedRepository)  
  .createFeed(newTempFeed);

 

테스트 결과

빠르게 나머지 코드들을 세팅하고 테스트를 해본다.
 
https://i.imgur.com/Juosa9E.png  

이제 정상적으로 START TRANSACTION이 한번 일어나고,
의도적인 에러를 터트려 롤백하게 했더니 마지막으로 무사히 ROLLBACK이 이루어진다.
 

transaction안에 AWS S3 objectCommand까지 있어 코드가 좀 어지러웠지만 그래도 예상되는 모든 Transaction-Rollback 테스트가 모두 통과되어 되어 한시름 놓았다.


  1. getConnection() 방식은 typeORM 0.3.x에서 deprecated 되었고, dataSource 방식으로 대체하면 된다. ↩︎