๐Ÿ’ป
Albert's Til
GitHub
  • ๋งค์ผ๋งค์ผ ์กฐ๊ธˆ์”ฉ ์„ฑ์žฅํ•˜๊ธฐ
    • README
    • CS
      • Network
      • HTTP
        • NO-CACHE
      • ์˜ค๋ฅ˜ ์ฝ”๋“œ
      • ORM ๋„๊ตฌ
      • Design Pattern
        • CQRS Pattern
          • Event Sourcing and CQRS pattern
        • Builder Pattern
    • DB
      • MySQL
        • Timeline
        • Pagination
        • Index
        • Database Performance Optimization Strategies
        • B+ tree
        • MySQL Connectors VS MySQL Shell(Scripting) VS MySQL Workbench
        • MySQL Storage Engine Architecture
      • Normalization & Denormalization
      • JPA
        • @Transactional
        • Why JPA?
        • About JPA
        • N+1 Issue
        • Index
        • ElementCollection&CollectionTable
        • orphanRemoval
        • CascadeType
        • Use Subselect
        • Dynamic Instance Creation
        • Paging
        • Order
        • Spefication
        • mappedBy
      • MongoDB
        • ObjectId
      • Why MySQL?
      • ACID properties of transactions
      • Between JPA and JDBC
      • Identifiers in Hibernate/JPA
    • Java
      • Jackson de/serialize
      • Collections.singletonList() vs List.of()
      • Manage dependencies in Gradle
      • Logging Level
      • Bean Validation
      • JVM Internals
        • Threads
          • Frame
        • Shared Between Threads
          • Classloader
            • Class Loader Hierarchy
            • Loading Linking Initialization
      • Java Collection Framework
      • Annotation
      • Generic
      • ๋””๋ฏธํ„ฐ ๋ฒ•์น™
    • Spring
      • Caching
      • Spring Integration Overview
        • ThreadPollTaskExecutor
        • Messaging Bridge
        • Channel Adapter
        • Poller
        • Configuration and @EnableIntegration
        • Message Endpoints
        • Message Channels
      • HATEOAS
      • @Autowired vs Constructor Dependency Injection
      • Spring Security
        • JWT ํ† ํฐ ์‚ฌ์šฉํ•œ ์ธ๊ฐ€
        • OAuth 2 Login
        • OAuth 2 ์ธ์ฆ
        • ์ธ๊ฐ€
        • ์ธ์ฆ
        • PasswordEncoder
      • IoC Container
      • Filter,Interceptor,AOP,Argument Resolver
      • Spring Annotation
      • About Spring
    • Kafka
      • Error Channel
    • Infra
      • Scale Up || Scale Out
      • Docker
        • Dockerfile
        • Docker Hub Deploy
        • Command
      • Cloud ์œ ํ˜•
        • Infrastructure as a Service
        • Platform as a Service
        • Software as a Service
      • ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ
        • ์—”์ง„์—‘์Šค(Nginx)
      • ์ฝ”๋“œ ์ž๋™ ๋ฐฐํฌ
        • Technical
      • AWS EC2
        • PEM(Privacy Enhanced Mail) ํ‚ค
      • AWS RDS
      • AWS S3
    • CodeSquad
      • Spring Boot Project 1์ฃผ์ฐจ ํšŒ๊ณ 
      • Spring Boot Project 2์ฃผ์ฐจ ํšŒ๊ณ 
      • Spirng Boot Project 3์ฃผ์ฐจ ํšŒ๊ณ 
      • Spring Boot Project 4์ฃผ์ฐจ ํšŒ๊ณ 
    • Foody Moody ํ”„๋กœ์ ํŠธ
      • Query Performance Comparison
      • HeartCount Asynchronous Issue
      • DeferredResult
      • ResponseBodyEmitter
      • SseEmitter (Spring)
      • Server-Sent Events (SSE)
      • ๊ธฐ์ˆ  ์Šคํƒ ์ ์šฉ ์ด์œ 
      • NO-CACHE(HTTP)
      • Transactional
    • DDD
      • AggregateId
    • Test
      • RestAssured
    • Coding and Algorithmic Problems
      • 819. Most Common Word
      • 344. Reverse String
      • 125. Valid Palindrome
      • 937. Reorder Data in Log Files
    • Node
      • Async... Await...
      • Custom Transactional Decorator Challenger
    • Python
      • Python Basic Grammar
        • Comments and Input/Output
        • Variable
        • Data type
        • Operations and syntax
        • List,Tuple,Dictionary,Set
        • Function
        • Conditional statement
        • Loop
    • HTML
      • HTML Basic
      • HTML Basic Tags
      • HTML Form Tags
      • HTML Table Tags
    • CSS
      • CSS Basic
      • CSS Practice
Powered by GitBook
On this page
  • ๋ฌธ์ œ
  • ์ฝ”๋“œ
  • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•
  • ํ•ด๊ฒฐ ๊ณผ์ •
  • Step1
  • Step1 ๋ฌธ์ œ์ 
  • Step2
  • ๊ฒฐ๋ก 

Was this helpful?

  1. ๋งค์ผ๋งค์ผ ์กฐ๊ธˆ์”ฉ ์„ฑ์žฅํ•˜๊ธฐ
  2. Node

Custom Transactional Decorator Challenger

๋ฌธ์ œ

  • NestJS์—์„œ TypeORM์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์„ ๊ตฌํ˜„ํ•  ๋•Œ, ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€ ์ฑ…์ž„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

  • ์ค‘๋ณต๋œ ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

NestJS ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” Transactions๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•

  • QueryRunner ์‚ฌ์šฉ

async createMany(users: User[]) {
  const queryRunner = this.dataSource.createQueryRunner();

  await queryRunner.connect();
  await queryRunner.startTransaction();
  try {
    await queryRunner.manager.save(users[0]);
    await queryRunner.manager.save(users[1]);

    await queryRunner.commitTransaction();
  } catch (err) {
    // since we have errors lets rollback the changes we made
    await queryRunner.rollbackTransaction();
  } finally {
    // you need to release a queryRunner which was manually instantiated
    await queryRunner.release();
  }
}
  • dataSource.transaction

async createMany(users: User[]) {
  await this.dataSource.transaction(async manager => {
    await manager.save(users[0]);
    await manager.save(users[1]);
  });
}

์ฝ”๋“œ

async updateInfo(
    data: EditUserInfoData,
  ): Promise<UserInfoResponse> {
    // QueryRunner Connect
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();  
    
    // Transaction Start
    await queryRunner.startTransaction();
    
    try {
    
    // ์‹ค์ œ ๋กœ์ง
      const userAccount = await queryRunner.manager.findOneBy(UserAccount, {
        id: id,
        deleted: false,
      });
      if (!userAccount) {
        throw new UserAccountNotFoundError();
      }
    
      userAccount.nickname = data.nickname;
      userAccount.address = data.address;
      userAccount.introduction = data.introduction
        ? data.introduction
        : userAccount.introduction;
      userAccount.updatedAt = new Date();
      await queryRunner.manager.save(UserAccount, userAccount);
      return {
        nickname: userAccount.nickname.value,
        address: userAccount.address.value,
        introduction: userAccount.introduction?.value,
      };
      
      
    } catch (err) {
    // Rollback
      await queryRunner.rollbackTransaction();
    } finally {
    // Release
      await queryRunner.release()
    }
  }

์œ„ ์ฝ”๋“œ์—์„œ try {} ๋ฌธ ์™ธ์˜ ์ฝ”๋“œ ์ค‘๋ณต๋œ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

  • TypeScript์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณตํ†ต ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•  ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

ํ•ด๊ฒฐ ๊ณผ์ •

Step1

@Transactional() Decorator๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ๊ณตํ†ต ๋กœ์ง์„ Decorator์—์„œ ๊ตฌํ˜„ํ•˜๋„๋ก ํ–ˆ๋‹ค.

  • ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง

@Transactional()
async updateInfo(data: EditUserInfoData) {
  const userAccount = await this.findById(data.userId);
  userAccount.nickname = data.nickname;
  userAccount.address = data.address;
  const now = new Date();
  userAccount.updatedAt = now;
  return this.userAccountRepository.save(userAccount, { transaction: false });
}
  • ๊ณตํ†ต ๋กœ์ง(Transactional Decorator)

export function Transactional(
  isolationLevel?: IsolationLevel,
): MethodDecorator {
  return (
    _target: any,
    _propertyKey: string,
    descriptor: PropertyDescriptor,
  ) => {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const dataSource: DataSource = this.dataSource;
      if (!dataSource) {
        throw new Error('DataSource is not injected');
      }

      // ํŠธ๋žœ์žญ์…˜์„ ์ด๋ฏธ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ถ”๊ฐ€๋กœ ํŠธ๋žœ์žญ์…˜์„ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค.
      const existsTransaction = args.find(
        (arg) => arg.connection !== undefined,
      );
      if (existsTransaction) {
        return originalMethod.apply(this, args);
      }

      // ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ํŠธ๋žœ์žญ์…˜์„ ์ƒ์„ฑํ•œ๋‹ค.
      const queryRunner: QueryRunner = dataSource.createQueryRunner();
      await queryRunner.connect();
      await queryRunner.startTransaction(
        isolationLevel ? isolationLevel : DEFAULT_ISOLATION_LEVEL,
      );

      try {
        args = args.concat(queryRunner);
        const result = await originalMethod.apply(this, args);
        await queryRunner.commitTransaction();
        return result;
      } catch (err) {
        await queryRunner.rollbackTransaction();
        throw err;
      } finally {
        await queryRunner.release();
      }
    };
  };
}

Step1 ๋ฌธ์ œ์ 

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ Connect์™€ ๊ณตํ†ต ๋กœ์ง์˜ Connect๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ์˜€์œผ๋‚˜ ์ด๋ฅผ ๋‹ฌ์„ฑํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.

  • ๊ณตํ†ต ๋กœ์ง์€ Connect 47

  • ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์€ Conenct 48

Step2

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ QueryRunner๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๊ณ , ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ์ด๋ฏธ ํŠธ๋žœ์žญ์…˜์ด ์‚ฌ์šฉ ์ค‘์ธ์ง€ ํ™•์ธํ•˜๊ณ , ํ•„์š”์— ๋”ฐ๋ผ ์ถ”๊ฐ€์ ์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜ QueryRunner๋ฅผ ์ฃผ์ž…ํ•œ๋‹ค.

  • ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง

@Transactional()
async updateInfo(data: EditUserInfoData, queryRunner?: QueryRunner) {
  const userAccount = await this.findByIdWithQueryRunner(
    data.userId,
    queryRunner,
  );
  userAccount.nickname = data.nickname;
  userAccount.address = data.address;
  userAccount.introduction = data.introduction
    ? data.introduction
    : userAccount.introduction;
  const now = new Date();
  userAccount.updatedAt = now;
  await this.saveWithQueryRunner(userAccount, queryRunner);
  return {
    nickname: userAccount.nickname.value,
    address: userAccount.address.value,
    introduction: userAccount.introduction?.value,
  };
}


async findByIdWithQueryRunner(
  id: ObjectId,
  queryRunner: QueryRunner,
): Promise<UserAccount> {
  const userAccount = await queryRunner.manager.findOneBy(UserAccount, {
    id: id,
  });
  if (!userAccount) {
    throw new UserAccountNotFoundError();
  }
  return userAccount;
}

async saveWithQueryRunner(
  userAccount: UserAccount,
  queryRunner: QueryRunner,
) {
  return queryRunner.manager.save(UserAccount, userAccount);
}
  • ๊ณตํ†ต ๋กœ์ง(Transactional Decorator)

export function Transactional(
  isolationLevel?: IsolationLevel,
): MethodDecorator {
  return (
    _target: any,
    _propertyKey: string,
    descriptor: PropertyDescriptor,
  ) => {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const dataSource: DataSource = this.dataSource;
      if (!dataSource) {
        throw new Error('DataSource is not injected');
      }

      // ํŠธ๋žœ์žญ์…˜์„ ์ด๋ฏธ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ถ”๊ฐ€๋กœ ํŠธ๋žœ์žญ์…˜์„ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค.
      const existsTransaction = args.find(
        (arg) => arg.connection !== undefined,
      );
      if (existsTransaction) {
        return originalMethod.apply(this, args);
      }

      // ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ํŠธ๋žœ์žญ์…˜์„ ์ƒ์„ฑํ•œ๋‹ค.
      const queryRunner: QueryRunner = dataSource.createQueryRunner();
      await queryRunner.connect();
      await queryRunner.startTransaction(
        isolationLevel ? isolationLevel : DEFAULT_ISOLATION_LEVEL,
      );

      try {
        args = args.concat(queryRunner);
        const result = await originalMethod.apply(this, args);
        await queryRunner.commitTransaction();
        return result;
      } catch (err) {
        await queryRunner.rollbackTransaction();
        throw err;
      } finally {
        await queryRunner.release();
      }
    };
  };
}
  • ๊ฒฐ๊ณผ

ํ•˜๋‚˜์˜ connect ์—์„œ ํ•˜๋‚˜์˜ transactional ์•ˆ์—์„œ ๋ชจ๋“  ๋กœ์ง์ด ์‹คํ–‰ํ•œ๋‹ค.

๊ฒฐ๋ก 

NestJS์™€ TypeORM์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์ด๊ณ , ๋ฉ”์„œ๋“œ์˜ ์ฑ…์ž„์„ ๋ถ„์‚ฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํƒ๊ตฌํ–ˆ๋‹ค.

  • Transactional Decorator์˜ ๋„์ž…

  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ์˜ ๋ถ„๋ฆฌ

  • ํ•˜๋‚˜์˜ Connect ๋‚ด์—์„œ์˜ ํŠธ๋žœ์žญ์…˜ ์‹คํ–‰

๊ฒฐ๋ก ์ ์œผ๋กœTransactional ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ ์‚ฌ์šฉ๊ณผ QueryRunner์˜ ์ ์ ˆํ•œ ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•ด, NestJS ๋ฐ TypeORM ํ™˜๊ฒฝ์—์„œ ํŠธ๋žœ์žญ์…˜์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ถ•ํ–ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ์— ๋Œ€ํ•œ ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ๋ช…ํ™•์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œ์ผฐ๋‹ค.

๋ฌผ๋ก ,์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์–ด๋–ค ์ถ”๊ฐ€์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น ์ง€๋Š” ์•„์ง ๋ถˆํ™•์‹คํ•˜๋‹ค. ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ง€์†์ ์œผ๋กœ ํ™•์ธํ•ด์„œ ๊ฐœ์„  ์ž‘์—…ํ•  ์˜ˆ์ •์ด๋‹ค.

Last updated 1 year ago

Was this helpful?