Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Mini

[Nest] Transaction 구현 본문

JS/Nest.js

[Nest] Transaction 구현

Mini_96 2024. 9. 22. 21:57

* 설계

  • 이미지가 생성되고
  • post 생성되고 를 묶어서
  • all or nothing으로 만들고자 함.

 

* 구현

  • 컨트롤러에서 dataSouce 를 주입받음.
  • 이걸로 db와 통신할수있는 api임.
  • 컨트롤러
  • 주의할점은, controller, service에서 같은 qr을 사용해야 트랜잭션이 적용된다는점임
  • 이를위해 서비스에서 getRepository 함수를 만듬.
@Controller('posts')
export class PostsController {
  constructor(
    private readonly postsService: PostsService, 
    private readonly dataSource: DataSource,
              ) {}
@Post()
@UseGuards(AccessTokenGuard)
async postPost(
  @User('id') userId : number,
  @Body() body : CreatePostDto,
  ){

  const qr = this.dataSource.createQueryRunner();

  await qr.connect();
  //이 시점에서 같은 qr을 사용해야 트랜잭션이 묶인다!! -> 즉, qr을 인자로 전달해야한다.
  await qr.startTransaction();

  //로직실행
  try{
    const post = await this.postsService.createPost(userId ,body);

    /**
     * body의 image 이름들에대해
     * 각각 정보들을 넣어서 이미지를 만듬(db에 저장)
     * 주인은 post
     */
    for(let i=0;i<body.images.length;i++){
      await this.postsService.createPostImage({
        post,
        order:i,
        path:body.images[i],
        type:ImageModelType.POST_IMAGE,
      });
    }
    await qr.commitTransaction();

    return this.postsService.getPostById(post.id);

  }
  catch (e){
    await qr.rollbackTransaction();
  }
  finally {
    await qr.release();
  }



}
  • 서비스
getRepository(qr? : QueryRunner){
  return qr? qr.manager.getRepository<PostsModel>(PostsModel) : this.postsRepository;
}

async createPost(authorId: number, postDto : CreatePostDto, qr?: QueryRunner){

  const repository = this.getRepository(qr);

  const post = repository.create({
    author: {
      id: authorId,
    },
    ...postDto,
    images: [],
    likeCount:0,
    commentCount:0,
  });

  const newPost = await repository.save(post);
  return newPost;
}

 

 

* create-image도 qr 구현

  • getRepository에서 자신의 모델의 리포지토리를 받아야함
  • 별도로 분리.
import { BadRequestException, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { ImageModel } from "../../common/entity/image.entity";
import { QueryRunner, Repository } from "typeorm";
import { CreatePostImageDto } from "./dto/create-image.dto";
import { basename, join } from "path";
import { POST_IMAGE_PATH, TEMP_FOLDER_PATH } from "../../common/const/path.const";
import { promises } from "fs";

@Injectable()
export class PostsImagesService{
  constructor(
    @InjectRepository(ImageModel)
    private readonly imageRepository : Repository<ImageModel>,
  ) {
  }
  
  getRepository(qr? : QueryRunner){
    return qr? qr.manager.getRepository<ImageModel>(ImageModel) : this.imageRepository;
  }

  /**
   * dto의 이미지 이름을 기반으로
   * 파일의 경로 생성
   * @param dto
   */
  async createPostImage(dto : CreatePostImageDto, qr: QueryRunner){
    const repository = this.getRepository(qr);

    const tempFilePath = join(TEMP_FOLDER_PATH, dto.path);

    try{
      await promises.access(tempFilePath); //존재확인,
    }
    catch(error){
      throw new BadRequestException('존재하지 않는 파일 입니다.' + error);
    }

    //파일 이름만 가져오기
    //public/temp/aaa.jpg -> aaa.jpg
    const fileName= basename(tempFilePath);

    const newPath = join(POST_IMAGE_PATH, fileName);

    //옮기기전에 save (rollback대비)
    const result = await repository.save({
      ...dto,
    });

    // 1->2로 파일 옮김.
    await promises.rename(tempFilePath, newPath);

    return result;
  }
}
  • 생성자주입
@Controller('posts')
export class PostsController {
  constructor(
    private readonly postsService: PostsService,
    private readonly dataSource: DataSource,
    private readonly postsImagesService: PostsImagesService,

 

주입 // 이러려고 imageService에 @Injectable 달아놓음!!

 

* 결과

임의로 에러를 발생시킨경우, 롤백된다. / 새로운 post가 생기지 않았다.