* 정보제공용 글이 아닌 개인 개발 일지 작성용입니다. 비판 환영!
1. 서론
개발하면서 우리는 다양한 오류를 마주한다.
통상적으로 트랜잭션은 하나의 작업 단위를 말하며, 특정 비즈니스 로직은 하나의 트랜잭션을 갖는다고 할 수 있다.
만일, DB 내의 여러 테이블에 Insert를 하는 로직이 있다고 가정해보자.
중간에 로직을 처리하는 도중 예상치 못한 오류가 발생한다면? 당연히 해당 로직이 지금까지 수행했던 모든 Insert 작업들을 Rollback시켜야한다. 데이터 무결성을 유지하기 위해서다.
이렇게 무결성을 지키기 위해 수행하는 로직 처리를 트랜잭션 처리라고 한다.
따라서 이번 시간에는 TypeORM을 활용해서 트랜잭션을 처리하는 방법에 대해서 구현을 해봐야겠다.
이번에는 공식 도큐먼트를 참조하려고 했는데, 다른 방법으로 처리했다. 공식 도큐먼트에서는 QueryRunner 를 이용하여 트랜잭션을 처리하는 것이 권장된다고 했는데, 내가 만드려는건 그냥 심심해서 만드는 토이프로젝트라서 일단 더 편한 방법을 사용하기로 했다.
바로 TypeORM에서 제공하는 Connection 객체를 받아 Entity Manager를 통해 트랜잭션을 처리하는 방법이다.
2. 구현
자, 만일 다음과 같은 로직이 있다고 가정해보자.
async create(createExampleDto: CreateExampleDto) {
await this.exampleRepository.save(createExampleDto);
throw new BadRequestException();
await this.exampleRepository.save(createExampleDto);
return await this.exampleRepository.save(createExampleDto);
}
상기 로직은 단순히 DB에 Insert를 수행하는 비즈니스 로직이다. 이때, 첫 번째 Insert 이후 의도적으로 Exception을 발생시켰다.
해당 API를 호출해보면,
예외가 발생했다. 이제 DB를 확인해보자.
예외가 발생했음에도 불구하고 Insert됨을 확인할 수 있었다. 그러면 예외가 발생했을 때, Insert 쿼리든, Update 쿼리든, DB의 데이터 상태를 변경하는 일련의 작업들을 모두 Rollback 시켜야 한다.
이제 트랜잭션 처리를 해보자.
@Injectable()
export class ExampleService {
constructor(
@InjectRepository(Example)
private readonly exampleRepository: Repository<Example>,
private connection: Connection, // Connection 객체 주입
) {}
async create(createExampleDto: CreateExampleDto) {
// Connection 객체를 통한 Transaction
await this.connection.transaction(async (manager: EntityManager) => {
await manager.getRepository(Example).save(createExampleDto);
throw new BadRequestException();
await manager.getRepository(Example).save(createExampleDto);
return await manager.getRepository(Example).save(createExampleDto);
});
}
}
간단하다. 생성자에서 TypeOrm이 제공해주는 Connection 객체를 주입한 다음, 해당 객체를 통해 transaction() 메소드를 호출한 뒤, 인자로 Entity Manager 타입을 전달받아 해당 Entity Manager로 조작하고자 하는 Entity의 Repository를 가져온다 - getRepository()
그리고 기존 요청 로직과 동일하게 save() 메소드에 DTO를 담아 Insert 시킨다.
이때 주의할 점은, Connection 객체를 통한 Transaction의 경우, 서비스 클래스의 생성자에서 주입한 ExampleRepository는 사용하지 못 한다는 점이다. 사용이야 할 수는 있지만, Transaction 기능이 동작하지 않는다.
앞서 봤던 로직과 달라진 점은 딱 두 군데이다. 첫 번째는 Connection 객체를 통한 Transaction을 진행하는 로직이 추가되었다는 것과, 생성자에서 주입한 Example Repository를 사용하지 않고 Entity Manager를 통해 직접 Repository를 받아와서 DB 요청 작업을 수행한다는 것이다.
이제 예상되는 로직은 다음과 같다.
1. Tranasction 진입
2. Insert 작업 수행
3. 예외 발생
4. 2번 과정에 대한 작업 Rollback
DB 내에 있는 데이터를 모두 지우고 이제 해당 API를 호출해보자.
동일하게 400 에러가 의도대로 발생하였고, DB를 확인해보면,
아무런 데이터도 Insert되지 않음을 확인하였다. 이는 예외가 발생했을 경우 모든 DB 요청을 Rollback하기 때문이다.
그러면 예외를 지우고 다시 실행해보자.
호출한 결과 정상적으로 Response가 반환됨을 확인했고, DB를 확인해보면
데이터가 잘 들어옴을 확인했다. QueryRunner를 사용하지 않으면 어떤 문제가 발생하는지는 더 알아봐야할 것 같다.
끗
'좌충우돌 산악회 홈페이지 만들기' 카테고리의 다른 글
[좌충우돌 산악회 홈페이지 만들기 #5] Nest.js에 Swagger OpenAPI 붙이기 (0) | 2022.04.19 |
---|---|
[좌충우돌 산악회 홈페이지 만들기 #4] Nest.js에서 Exception Filter 만들기 (0) | 2022.04.17 |
[좌충우돌 산악회 홈페이지 만들기 #3] 공통 Response를 만들어보자 (0) | 2022.04.17 |
[좌충우돌 산악회 홈페이지 만들기 #2] TypeORM을 활용한 DB 연동과 Cross-env 및 Dotenv 세팅, 예제 컨트롤러/서비스 개발! (0) | 2022.04.17 |
[좌충우돌 산악회 홈페이지 만들기 #1] 필요한 것들을 나열해보자 (0) | 2022.04.15 |