3.0 이상의 typeorm 을 가지고 트랜잭션 관리를 해보자.
레퍼런스가 너무 많아서 그냥 기록용으로 작성하려고 한다.
// room.repository.ts
import { Injectable } from '@nestjs/common';
import { RoomEntity } from 'lib/database/entity';
import { DataSource, Repository } from 'typeorm';
@Injectable()
export class RoomRepository extends Repository<RoomEntity> {
constructor(private dataSource: DataSource) {
super(
RoomEntity,
dataSource.createEntityManager(),
dataSource.createQueryRunner(),
);
}
async createRoom(name: string): Promise<RoomEntity> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const result = await queryRunner.manager.save(RoomEntity, { name: name });
await queryRunner.commitTransaction();
return result;
} catch (error) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
}
자세히 보면 생성자 부분을 잘 봐야 한다.
상속받은 Repository class 에
RoomEntity
)dataSource.createEntityManager()
)dataSource.createQueryRunner()
)를 주입시켜줘서 사용한다.
위와 같이 작성하는것이 일반적인데, repo 단에서 로직 구현할떄 마다 저렇게 할 수는 없으니, 코드를 줄여보자.
상속받은 Repository class 에는 EntityManager 에 접근할 수 있는 manager 라는 멤버변수가 있다.
export declare class Repository<Entity extends ObjectLiteral> {
/**
* Entity target that is managed by this repository.
* If this repository manages entity from schema,
* then it returns a name of that schema instead.
*/
readonly target: EntityTarget<Entity>;
/**
* Entity Manager used by this repository.
*/
readonly manager: EntityManager;
/**
* Query runner provider used for this repository.
*/
readonly queryRunner?: QueryRunner;
/**
* Entity metadata of the entity current repository manages.
*/
get metadata(): import("..").EntityMetadata;
constructor(target: EntityTarget<Entity>, manager: EntityManager, queryRunner?: QueryRunner);
manager 를 통해서 EntityManager class 에 접근해서 보면 transaction 함수가 보인다.
export declare class EntityManager {
/**
* Wraps given function execution (and all operations made there) in a transaction.
* All database operations must be executed using provided entity manager.
*/
transaction<T>(runInTransaction: (entityManager: EntityManager) => Promise<T>): Promise<T>;
/**
* Wraps given function execution (and all operations made there) in a transaction.
* All database operations must be executed using provided entity manager.
*/
transaction<T>(isolationLevel: IsolationLevel, runInTransaction: (entityManager: EntityManager) => Promise<T>): Promise<T>;
설명을 잘 읽어보면,
주어진 함수를 하나의 transaction 으로 감싸서 실행할 수 있다. 그리고 반드시 주어진 entityManager 를 이용해야 한다.
라고 나와있다.
이거를 사용하면 되겠다 생각해서, 다음과 같이 transaction 안에 실행시킬 함수의 구현체를 넣어주면 구현할 수 있다.
대신, transaction 안에서는 에러가 발생하면 unhandled error 로 처리되기 때문에 반드시 안에서 try ~ catch 구문으로 에러를 핸들링해줘야 한다.
try {
await this.manager.transaction(async (entityManager) => {
try {
await entityManager.save(RoomEntity, { name: name });
} catch (error) {
throw new Error('duplicated key');
}
});
} catch (error) {
throw error;
}
그래서 다음과 같이 구현해냈다.
// room.repository.ts
@Injectable()
export class RoomRepository extends Repository<RoomEntity> {
constructor(private dataSource: DataSource) {
super(
RoomEntity,
dataSource.createEntityManager(),
dataSource.createQueryRunner(),
);
}
async getAllRoomIds(): Promise<RoomEntity[]> {
return await this.find();
}
async getRoom(id: string): Promise<RoomEntity> {
return await this.findOne({
where: {
id: id,
},
});
}
async createRoom(name: any): Promise<void> {
try {
await this.manager.transaction(async (entityManager) => {
try {
await entityManager.save(RoomEntity, { name: name });
} catch (error) {
throw new Error('duplicated key');
}
});
} catch (error) {
throw error;
}
}
}