JPA

 

 

 

간단한 게시판 프로젝트에서 게시글 수정을 하는 메서드를 호출 하는데 위와 같은 에러가 발생했습니다.

* JPQL을 사용해서 update 쿼리문을 작성했습니다.

 

 

 

구글링을 한 결과 해당 메서드에 @Transactional 어노테이션을 달거나,

Repository에 작성한 JPQL문 위에 @Transactional 어노테이션을 다는 방식으로 해결이 가능했습니다.

 

해당 메서드 위 혹은 JPQL문 위에 트랜잭셔널 어노테이션 기입

 

여기서 왜 @Transactional 어노테이션 의문이 생겼습니다.

구글링을 했지만 해당 내용에대해 명쾌하게 정리되어 있는 글이 없었습니다.

 

그래서 update 쿼리문에서 Transaction 이 필요한 이유를 검색 했습니다.

그 결과 update 문은 해당 테이블의 컬럼의 모든 데이터를 변하게 할 수 있는 강력한 쿼리문이기 때문에,

실수로 작성을 하게되면 엄청난 문제를 초래한다는 내용을 찾게 되었습니다.

 

https://woogie-db.tistory.com/13

 

[MSSQL] 트랜잭션 설명 및 사용법

오늘은 MS-SQL의 UPDATE문을 전체적으로 설명드리겠습니다. UPDATE문 역시 주의해야하는 명령어이므로 꼭 트랜잭션 안에서 사용해야 합니다...... (SELECT문을 제외하고는 대부분 주의가 필요합니다....

woogie-db.tistory.com

 

위 글을 보면 update 발생하는 문제를 방지하기 위해 select문을 먼저 실행 후에 update 쿼리문을 사용하는 방식을 권합니다.

여기서 select 문과 update문을 트랜잭션으로 묶어서 해당 쿼리문을 실행하다 문제가 생기면 롤백하는 것입니다.

 

JPA에서도 이런 문제를 방지하기 위해 update문을 실행하기 전에 select문이 먼저 실행되는 것을 볼 수 있었습니다.

결국 JPA가 우리가 주의해야 할 점들을 알아서 처리해준다는 것을 알게 되었습니다.

 

위 에러를 겪고 고민하고 해결하면서 코딩을 하면서 내가 개선하고 해야할 것들에 대해 정리해봤습니다.

개선할점

  • 결국 JPA를 사용하기 전에 SQL에 대한 공부가 선행 되어야 한다는 것을 느꼈습니다.
  • JPA 사용 시 show-sql 설정을 활용해서 호출하는 쿼리문을 로그에서 확인
    • 호출되는 쿼리문을 보고 하이버네이트의 내부동작에 대해서 공부 필요

 

 

반응형

📚 참고자료

  • [10분 테코톡] 샐리의 트랜잭션 => 링크

0️⃣ 트랜잭션 개념 공부를 하게 된 계기

스프링을 사용하면서 @Transactional 이라는 어노테이션을 적용해본 적이 있습니다.

메서드 위에 해당 어노테이션을 적용하면 메서드 안에 있는 쿼리들이 하나의 단위로 묶인다는 대략적인 용도만 고 있습니다. 그래서 @Transactional 어노테이션의 정확한 개념을 모르고 사용했기 때문에 의문점이 많이 들었습니다.

  • 트랜잭션이란?
  • 트랜잭션을 사용해야 하는 상황은?
  • 스프링에서는 트랜잭션을 어떻게 지원하는지?

 

 

 

 

1️⃣ 트랜잭션이란?

  • 더이상 나눌 수 없는 가장 작은 하나의 단위를 의미
  • 데이터베이스에서는 트랜잭션을 조작함으로써 사용자가 데이터베이스에 대한 완전성을 신뢰할 수 있도록 함
  • 모든 데이터베이스는 자체적으로 트랜잭션을 지원
  • 하나의 명령을 실행했을 때 데이터베이스가 온전히 그 명령을 실행해주는 것
    => 성공하면 커밋(Commit)
    => 실패하면 롤백(Rollback)
  • 데이터베이스는 기본적으로 트랜잭션을 관리하기 위한 설정을 갖고 있습니다.
    • 명령을 끝마칠 때까지 수행 내역을 로그에 저장해둡니다.
      - 데이터베이스에 반영된 내용을 재반영하기 위한 redo log
      - 수행을 실패해 이전의 상태로 되돌리는 undo log
      * 두 개의 log를 이용해 트랜잭션을 지원합니다.

 

 

 

2️⃣ 트랜잭션을 사용하는 상황(예시)과 성질

트랜잭션에서 가장 흔히 볼 수 있는 입출금을 예시로 들어보겠습니다.

 

  1. A가 B에게 만원을 송금하려고 합니다.
  2. A의 계좌에 만원보다 많은 금액이 있는지 확인합니다.
  3. A의 계좌에 만원보다 많은 금액이 있다면 만원을 차감합니다.
  4. 차감한 만원의 금액을 B의 계좌에 더해줍니다.

 

위 업무는 순서를 나눠놨지만 절대로 분리되거나 일부만 실행되면 안 되는 하나의 작업입니다.

이렇게 절대로 깨져서는 안되는 하나의 작업을 트랜잭션이라고 합니다.

 

 

트랜잭션의 네가지 성질

  • 원자성(Atomicity)
    - A가 B에게 송금을 하는데 A의 계좌에서 만원이 차감만 되고 작업이 종료되면 문제가 발생합니다.
    - 이렇듯 일부만 실행되는 경우는 없다는 원자성을 지닙니다.
    - [롤백]1번부터 4번까지 연결된 하나의 작업에서 어느 하나의 명령이라도 실패하면 롤백을 하게 됩니다.
    - [트랜잭션 커밋]반대로 1번부터 4번까지 작업이 성공적으로 수행하면 수정된 내용 데이터베이스에 반영
    - 롤백이나 커밋이 실행되어야 트랜잭션이 종료됩니다.

  • 일관성(Consistency)
    - 데이터베이스 내의 상태 혹은 계층 관계
    - 컬럼의 속성이 항상 일관되게 유지되어야 함
    - 컬럼의 속성이 수정되었다면 trigger를 통해 일괄적으로 모든 데이터베이스에 적용해야 합니다.
  • 지속성(Durability)
    - 트랜잭션이 성공적으로 수행되어 커밋되었다면 어떠한 문제가 발생하더라도 데이터베이스에 그 내용이
    영원히 지속되어야한다는 지속성이 있습니다.
    - 이를 위해 모든 트랜잭션은 로그로 남겨져 어떠한 장애에도 대비할 수 있도록 합니다.

  • 독립성(Isolation)
    - 트랜잭션 수행 시 다른 트랜잭션이 끼어들 수 없고 각각 독립적으로 실행된다는 성질
    DB 독립성의 문제점(더보기 클릭)
    더보기
    하지만 데이터베이스에 작업이 들어왔을 때 모든 작업의 독립성을 보장해 하나씩 순차적으로 진행하게 된다면, CPU는 DBMS보다 인풋, 아웃풋이 빈번히 수행되기 때문에 트랜잭션을 순차적으로 시행하면 CPU는 점점
    응답을 기다리는 시간이 길어지게 됩니다. 그 결과 프로그램이 비효율적으로 동작하는 문제가 발생합니다.

 

이처럼 데이터베이스에 저장된 데이터의 무결성과 동시성의 성능을 지키기 위해 트랜잭션의 설정이 중요한 것입니다.

여러 명령을 하나의 트랜잭션으로 묶고 싶은 경우 개발자가 직접 트랜잭션의 경계설정을 통해 트랜잭션 명시

 

 

 

 

3️⃣ 스프링에서 지원하는 트랜잭션

 

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
    void commit(TransactionStatus status);
    void rollback(TransactionStatus status);
}

 

  • 트랜잭션 추상화 인터페이스인 PlatformTransactionManager를 제공
    • getTransaction 메서드
      - 파라미터로 전달되는 TransactionDefinition에 따라 트랜잭션을 시작
      - 트랜잭션을 문제없이 마치면 commit을, 문제가 발생하면 Rollback을 호출
    • getTransaction부터 commit이나 rollback을 하는 부분까지가 트랜잭션의 경계설정입니다.
  • 다양한 DataSource에 맞게 트랜잭션을 관리

 

 

스프링이 제공하는 다양한 트랜잭션 매니저 구현체

 

  1. DataSourceTransactionManager
    - JDBC에 사용되는 매니저
    - 하나의 데이터베이스를 사용하거나 각각의 데이터를 독립적으로 사용하는 로컬 트랜잭션에 사용

  2. JpaTransactionManager
    - JPA에 사용되는 매니저
    - 하나의 데이터베이스를 사용하거나 각각의 데이터를 독립적으로 사용하는 로컬 트랜잭션에 사용

  3. JtaTransactionManager
    - 하나 이상의 데이터베이스가 참여하는 경우 글로벌 트랜잭션에 사용되는 JtaTransactionManager 사용
    - 여러 개의 데이터베이스에 대한 작업을 하나의 트랜잭션에 묶을 수 있고, 다른 서버에 분산된 것도 묶을 수 있음

 

 

🍃 어노테이션을 기반으로 트랜잭션을 설정하는 방법

 

  @Transactional
    public void addRestaurantFoods(
            Long restaurantId,
            List<FoodRequestDto> requestDtoList
    ) {
        Optional<Restaurant> foundRestaurant = restaurantRepository.findById(restaurantId);

        checkRestaurant(foundRestaurant);
        Restaurant restaurant = foundRestaurant.get();

        for (FoodRequestDto requestDto : requestDtoList) {
            String foodName = requestDto.getName();
            int foodPrice = requestDto.getPrice();

            checkDuplicateRestaurantFood(restaurant, foodName);

            checkFoodPrice(foodPrice);

            Food food = Food.builder()
                    .name(foodName)
                    .price(foodPrice)
                    .restaurant(restaurant)
                    .build();

            foodRepository.save(food);
        }
    }

 

  • 어노테이션이 적용된 메서드는 메서드 시작부터 트랜잭션이 시작
    => 메서드를 성공적으로 끝마치면 트랜잭션 커밋
    => 도중에 문제가 발생하면 롤백
  • 어노테이션은 데이터베이스에 여러번 접근하면서 하나의 작업을 수행하는 서비스 계층 메서드에 붙인다.

 

 

 

반응형

+ Recent posts