[TypeORM] Transaction

hahaha·2021년 11월 14일
1

TypeORM

목록 보기
4/4

Transaction in DB

  • 작업 수행에 필요한 DB 연산들(SQL문)의 모임
  • 논리적인 작업의 단위
  • DB의 무결성, 일관성 보장을 위해 하나의 트랜잭션으로 정의하여 관리되어야 함

ACID 특성

Atomicity 원자성

  • all-or-nothing: 모두 정상 실행되거나 하나도 실행되지 않아야 한다.

Consistency 일관성

  • 트랜잭션 수행이 완료된 이후, DB가 일관된 상태를 유지해야 한다.

Isolation 격리성

  • 수행 중인 트랜잭션이 완료되기 전에, 다른 트랜잭션이 중간에 접근할 수 없다.

Durability 지속성

  • 트랜잭션 수행이 완료된 이후, DB에 반영된 결과는 영구적이여야 한다.

-> ACID 특성을 보장하기 위해 회복, 병행 제어 기능이 필요하다.

주요 연산

commit

  • 트랜잭션의 수행이 성공한 경우, 결과가 DB에 반영되고 일관된 상태를 지속한다.

rollback

  • 트랜잭션의 수행이 실패한 경우, 수행한 결과를 취소하고 수행 전의 일관된 상태로 되돌아간다.

Transaction in TypeORM

  • 콜백함수 내에서 트랜잭션을 적용시킬 것을 정의한다.
  • 항상 제공된 entity manager의 인스턴스를 사용해야 한다.
    - 전역 매니저를 사용할 경우, 중간에 에러가 발생해도 롤백이 제대로 이뤄지지 않는다.

사용하기

Connection

await getConnection().transaction(async transactionalEntityManager => {
	await transactionalEntityManager.save(users);
  	// ...
});

EntityManager

await getManager().transaction(async transactionalEntityManager => {});

Decorator

  • @Transaction
    - 격리 수준 설정: @Transaction({ isolation: "SERIALIZABLE" })
  • @TransactionManager
    - transaction entity manager를 제공한다.
    - 데이터 조작은 제공된 manager만을 사용해야 한다.
@Transaction()
save(@TransactionManager() manager: EntityManager, user: User) {
  return manager.save(user);
}
  • @TransactionRepository
@Transaction()
save(user: User, @TransactionRepository(User) userRepository: Repository<User>) {
  return userRepository.save(user);
}

Query Runner

-주요 메소드: startTransaction, commitTransaction, rollbackTransaction, release

import {getConnection} from "typeorm";

// get a connection and create a new query runner
const connection = getConnection();
const queryRunner = connection.createQueryRunner();

// establish real database connection using our new query runner
await queryRunner.connect();

// now we can execute any queries on a query runner, for example:
await queryRunner.query("SELECT * FROM users");

// we can also access entity manager that works with connection created by a query runner:
const users = await queryRunner.manager.find(User);

// lets now open a new transaction:
await queryRunner.startTransaction();

try {

    // execute some operations on this transaction:
    await queryRunner.manager.save(user1);
    await queryRunner.manager.save(user2);
    await queryRunner.manager.save(photos);

    // commit transaction now:
    await queryRunner.commitTransaction();

} catch (err) {

    // since we have errors let's rollback changes we made
    await queryRunner.rollbackTransaction();

} finally {

    // you need to release query runner which is manually created:
    await queryRunner.release();
}

Isolation Level 이란?

  • 동시에 여러 트랜잭션이 처리될 때, 트랜잭션 간의 고립된 정도
  • 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것
    (자세한 내용은 Locking과 각 레벨의 상세 설명 찾아보기)

Isolation Level 설정하기

  • 첫 번째 매개변수에 격리 수준을 전달한다.
  • 종류: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
await getManager().transaction("SERIALIZABLE", manager => {});

Transaction In NestJS

  • 트랜잭션의 다양한 전략 중 QueryRunner 클래스 사용을 권장한다.
  • 데코레이터를 사용한 트랜잭션 제어를 권정하지 않는다.

사용하기

  1. Connection 객체 삽입
@Injectable()
export class UsersService {
  constructor(private connection: Connection) {}
}
  1. 트랜잭션 생성
async createMany(users: User[]) {
  
  // 1. queryRunner 사용
  const queryRunner = this.connection.createQueryRunner();
  
  await queryRunner.connect();
  await queryRunner.startTransaction();

  try {  
	await queryRunner.manager.save(users[0]);
    
    await queryRunner.commitTransaction();
  } catch (err) {
    await queryRunner.rollbackTransaction();
  } finally {
    await queryRunner.release();
  }
  
  // 2. callbackk 스타일 사용
  await this.connection.transaction(async manager => {
    await manager.save(users[0]);
  });
}

참고자료

TypeORM - Transactions
NestJS - Transactions

profile
junior backend-developer 👶💻

1개의 댓글

comment-user-thumbnail
2022년 9월 20일

This is a great idea, it will definitely be shared widely, thanks for sharing it with us quordle

답글 달기