score를 insert 하고 update 할 수 있는 엔드포인트를 형성하는 작업을 하던 중 처음으로 트랜잭션을 사용해야하는 경우가 있었다.
그래서 이번 일지에서는 트랜잭션이 뭐고 어떤 상황에서 사용했는지, 어떻게 사용했는지에 대해 이야기해보려한다.
쪼갤 수 없는 최소 단위의 업무를 의미한다.
이렇게 처리를 해야하는 이유로는 DB 쿼리가 실패했을 경우 데이터를 안전하게 되돌리기 위해서이다.
우리가 중요하게 생각하는 돈에 대한 것 중 입출금에 대해서 트랜잭션으로 반드시 관리를 해야한다.
A가 10000원을 B에게 송금을 한다면 이 과정 속에서 A의 잔고가 -10000원이 되어야하고 B의 잔고는 +10000원이 되어야한다.
만약 중간에 오류가 나서 A만 잔고가 감소하게 된다면 10000원의 행방이 사라진다.
이런 사태를 방지하기 위해 저 일련의 과정을 '트랜잭션'으로 지정한다.
쿼리들이 성공적으로 모두 처리된다면 commit
하여 트랜잭션이 성공적으로 종료되었음을 트랜잭션 매니저에게 알린다.
만약 중간에 오류가 발생했다면 rollback
을 수행하여 트랜잭션이 시작되기 전의 상태로 되돌리고 exception을 던져준다.
spring에서는 트랜잭션을 관리하기 위해 어노테이션을 사용할 수 있다.
현재 내가 하고 있는 nestJS 프로젝트에서는 TypeORM의 DataSource를 활용하여 관리할 수 있다.
먼저 트랜잭션을 관리했어야하는 상황은 다음과 같다.
프론트에서 업데이트된 스코어 리스트들을 받아온다.
스코어 리스트들을 하나씩 순회하면서 각 스코어 레코드별로 업데이트를 진행한다.
이를 위해서 공식문서를 참고하여 다음과 같이 코드를 작성하였다.
async updateScores(applicationId: number, updateScoresDto: UpdateScoresDto){
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
for(const newScore of updateScoresDto.scores){
await queryRunner.manager.update(ScoreBoard, {
id: newScore.id, application: applicationId
},
{score: newScore.newScore})
}
await queryRunner.commitTransaction(); //성공 시 커밋
} catch (err) {
console.log(err);
await queryRunner.rollbackTransaction(); //실패 시 롤백
throw new InternalServerErrorException("트랜잭션 중 오류 발생, 롤백 진행완료");
} finally {
await queryRunner.release(); //쿼리러너 종료
}
}
공식문서에서는 DataSource의 queryRunner를 사용하는 것을 권장한다.
기본적인 메소드 순서는 connect
, startTransaction
, commitTransaction
or rollbackTransaction
그리고 release
가 있다.
이 사이사이에 우리가 할 쿼리들을 manager든 repository등 사용하여 구현하면 된다.