날짜별/TIl 오늘 뭘 배웠지
2022-03-28
YoungDogg
2022. 3. 28. 22:50
ACID
nestjs에서 지원하는 ACID를 썼다. 두 개의 트랜잭션에서 하나가 등록하면 다른 하나는 조회할 때 바뀌기 전 데이터가 조회될 수도 있기 때문에 ACID를 써야했다.
nestjs에선 기본으로 Connection으로 지원한다. Connection을 변수로 받고 try...catch...finally로 진행한다.
try문에 실행될 코드를 넣고 catch문에 rollbackTransaction을 하며 finally문에 release를 한다.
Isolation
startTransaction괄호 안에 넣어서 쓰는 것이며
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
이렇게 네 개가 있다.
- READ UNCOMMITTED
다른 트랜잭션의 변경 여부에 상관없이 읽을 수 있다. 따라서 오류가 가장 많으며 빠르다.
- READ COMMITTED
다른 트랜잭션의 변경 여부를 따진다. 하지만 UNDO라는 백업 영역의 값을 가져오기 때문에 트랜잭션끼리 다른 값이 나온다.
- REPEATABLE READ
트랜잭션 사건마다 ID를 만든다. ID가 낮은 사건을 다루기 때문에 오류를 줄일 수 있다.
하지만 Phantom Read가 발생하기도 한다.
import { ConflictException, Inject, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Connection, getRepository, Repository } from "typeorm";
import { Product } from "../product/entities/product.entity";
import { User } from "../user/entities/user.entity";
import { Order, ORDER_STATUS_ENUM } from "./entities/order.entity";
@Injectable()
export class OrderService{
constructor(
@InjectRepository(Order)
private readonly orderRepository:Repository<Order>,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
@InjectRepository(Product)
private readonly productRepository: Repository<Product>,
private readonly connection: Connection // <-- ACID를 위한 것
){}
async create({currentUser, impUid, productId, status}){
const queryRunner = await this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction('REPEATABLE READ');
try{
// user 있는 건지 확인
const user = await this.userRepository.findOne({email:currentUser.email});
if(!currentUser) throw new ConflictException('없는 유저입니다.')
// 물품 있는 건지 확인
const product = await this.productRepository.findOne({id:productId});
if(!product) throw new ConflictException('물품이 없습니다');
status = status.toUpperCase();
let statusEnum:ORDER_STATUS_ENUM;
if(status === "PAYMENT") statusEnum = ORDER_STATUS_ENUM.PAYMENT;
else if(status === "EXAMINATION") statusEnum = ORDER_STATUS_ENUM.EXAMINATION;
else if(status === "ONTHEWAY") statusEnum = ORDER_STATUS_ENUM.ONTHEWAY;
else if(status === "DELIVERED") statusEnum = ORDER_STATUS_ENUM.DELIVERED;
else if(status === "CANCEL") {statusEnum = ORDER_STATUS_ENUM.CANCEL; }
else throw new ConflictException("적절한 OrderEnum이 아닙니다");
const orderTransaction = await this.orderRepository.create({
user,
impUid,
product,
status: statusEnum //결제완료일 때
});
await this.orderRepository.save(orderTransaction)
return orderTransaction;
}catch(error){
queryRunner.rollbackTransaction();
}finally{
await queryRunner.release();
}
}
async delete({orderId}){
const queryRunner = await this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction('REPEATABLE READ');
try{
const result = await this.orderRepository.softDelete({id:orderId})
return result.affected ? true: false
}catch(error){
queryRunner.rollbackTransaction();
}finally{
await queryRunner.release();
}
}
async findAll(){
const queryRunner = await this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction('REPEATABLE READ');
try{
return await this.orderRepository.find()
}catch(error){
queryRunner.rollbackTransaction();
}finally{
await queryRunner.release();
}
}
async findOneById({orderId}){
const queryRunner = await this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction('REPEATABLE READ');
try{
return await this.orderRepository.findOne({id: orderId})
}catch(error){
queryRunner.rollbackTransaction();
}finally{
await queryRunner.release();
}
}
}