관리 메뉴

제뉴어리의 모든것

[Section3] [Spring MVC] 트랜잭션(Transaction) 본문

코드스테이츠/정리 블로깅

[Section3] [Spring MVC] 트랜잭션(Transaction)

제뉴어리맨 2022. 9. 7. 03:11

트랜잭션이란?

물리적으로는 여러개일 수 있으나, 논리적으로는 하나인 작업단위로써

부분성공이 불가능한 최소 작업 단위이다.

트랜잭션안에 여러개의 물리적 작업이 존재할 경우, 모두 성공하거나 하나라도 실패하면 모두 실패하여야한다. 

쉽게 말해, 더 이상 쪼갤 수 없는 최소 작업 단위이다.

트랜잭션의 ACID 원칙

  1. 원자성
    트랜잭션이란 화학에서 말하는 원자처럼,
    더이상 쪼갤 수 없는 작업단위여야 한다는것이다.
    물론, 트랜잭션 안에는 여러 작업이 존재 할 수 있으나 그 여러 작업이 부분 성공, 부분 실패가 불가능하다는것이다.
    트랜잭션의 결과는 "성공" 혹은 "실패"의 결과만 존재한다.

  2. 일관성
    트랜잭션이 성공할 경우, 항상 의도한 대로 일관된 결과를 도출해야한다는 것이다.
    트랜잭션의 성공시에 의도하지 않거나, 일관되지 않은 결과가 나와선 안된다.
    예를 들어, 고객이 상품을 하나 구매할때마다 1포인트씩 적립이 되기로 했는데
    어떤 고객은 상품을 구입하니까 2포인트가 올라가거나 하는 일관적이지 않은 결과가 존재해선 안되고 의도한대로 1포인트씩 적립되어야 한다는것이다.
    그러나, 상품의 가격마다 적립되는 포인트가 다른 적립 방법도 있을것이다.
    마치 일관성이 없는것처럼 보이지만, 상품의 가격에따라 적립되는 포인트 규칙이 있고, 해당 규칙에 따라 적립이 되는것이므로 의도한 대로 결과가 도출되는것이므로 일관성을 지킨다고 할 수 있다.

  3. 격리성
    여러 트랜잭션이 존재할 경우 각각의 트랜잭션은 다른 트랜잭션의 작업에 관여하거나, 영향을 주어선 안되며 자신의 작업만을 수행하여야 한다는것이다.

  4. 지속성
    성공한 트랜잭션의 결과는 휘발되는것이 아니라, 지속 가능해야 한다는것이다.
    DB를 예를 들자면, update, create, delete 같은 처리들을 한 후 최종적으로 commit 까지 하여 성공한 결과는 계속 DB에 반영이 되어 있어야 한다는것이다. commit을 한 후 갑자기 데이터가 rollback 되거나 해선 안된다.
    물론 처리 결과는 이후에 또 다른 update, create, update 로 인해 달라 질 수는 있다.
    그러나 그것은 다른 트랜잭션의 처리 이므로 지속성과는 관련이 없다.

 

커밋과 롤백

  • 커밋
    DB에 처리 결과를 완전히 반영하는 명령을 말한다.
    update, create, delete 처리를 내부적으로 하였어도 최종적으로 commit 을 하지 않으면, 데이터베이스에 처리 결과는 반영 되지 않는다.
    commit이 되면 update, create, delete 등을 수행하던 해당 트랜잭션은 종료가 된다.

  • 롤백
    트랜잭션 시작 이후부터 commit이 되기 전까지 처리한 결과를 트랜잭션 시작 이전의 상태로 되돌리는 것을 말한다.
    쉽게 말해, 원상복구이다.

MySQL 상에서 커밋과 롤백 명령어의 예

BEGIN TRANSACTION; //트랜잭션 시작
insert into  MEMBER VALUES //처리 내용 
			(1, now(), now(), 'hgd1@gmail.com', 'MEMBER_ACTIVE', '홍길동1', '010-1111-1111');
ROLLBACK; //롤백
COMMIT; //커밋

위에 쿼리를 MySQL에서 실행을 하면 최종적으로 아무런 처리도 되지 않고 기존 상태 그대로의 결과를 보인다.

 

 

 

JPA 내부적으로 진행 되는 트랜잭션의 처리 코드 예

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;

@Configuration
public class JpaBasicConfig {
    private EntityManager em;
    private EntityTransaction tx;

    @Bean
    public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
        this.em = emFactory.createEntityManager(); //EntityManager 얻음
        
	
				this.tx = em.getTransaction(); //EntityTransaction 얻음

        return args -> {
            tx.begin(); //트랜잭션 시작
            Member member = new Member("hgd@gmail.com");

            em.persist(member); //영속화
					
            tx.commit();    // 커밋, 트랜잭션의 종료 시점이라고 볼 수 있다.
        };
    }
}

 

  • EntityManager : JPA 에서 배운 그 Entity Manager이다.
    인터페이스이이다. EntityManager의 구현체들은, 생성하거나 수정된 Entity를 영속성 콘텍스트에 넣어주는 기능을 한다.

  • EntityTransaction : 트랜잭션을 제어하는 인터페이스로써,
    Entity Manager로부터 구현체를 얻는다.

 

트랜잭션의 의미

위에서 트랜잭션의 예를 DB를 들어 설명하곤 했지만,

트랜잭션이란 DB상에서만 쓰이는 개념이 아니다.

모든 분야에서 사용될 수 있으며, 프로그래밍에서도 비즈니스 로직에 대해서 트랜잭션의 의미를 가질 수 있다.

예를들어, 

"애기 키우기"란 트랜잭션이 있다.

애기 키우기 트랜잭션 안에는 애기를 낳아야하고, 애기를 재우고, 애기에게 밥을 먹여야하고 엄청 많은 처리들이 있다.

근데 이중에, 애기를 낳고 재우기는 하는데 애기에게 밥을 먹이는 처리를 하지 않는다면...

그건 아마도 뉴스에 나올 일이다.

이런것처럼 트랜잭션이란 내부적으로 여러 처리가 있을 수 있으나, 마치 하나의 작업처럼 쪼개서 처리 할 수 없는 작업 단위를 말한다.

프로그래밍에서도 그렇다.

클라이언트에게서 멤버 가입 요청이 왔다.

그런데 현재 사측의 방침은 멤버가 가입을 하면 해당 멤버에게 가입축하 이메일을 보내기로 되어있다.

그렇다면, "멤버 가입 로직" 과 "이메일 전송" 로직은 비즈니스 로직상 하나의 트랜잭션이다.

왜냐하면 어떤 멤버가 가입하였을때, 서버에서 가입만 시키고 이메일을 전송 안한다던가,

또는 어떤 멤버가 가입을 하였는데, 서버에서 가입을 시키지 않고 이메일 전송만 한다던가 하는 결과는 하나의 처리로 정의한 사측의 방침에 어긋나기 때문이다.

 

즉, 트랜잭션이란 모든 부분에서 사용되는 개념이다.

 

 

스프링에서 트랜잭션 적용 방법

  • @Transactional 적용
    트랜잭션을 적용하고 싶은곳에 @Transactional 애노테이션을 붙이는 방법으로, 가장 많이 사용하고 쉬운 방법이다.
    해당 애노테이션은 클래스 레벨과 메소드 레벨에 붙일 수 있다.
    애노테이션이 붙어서 하나의 트랜잭션으로 묶여서 처리 되는 영역은,

    repository가 수행하는 처리들을 하나의 트랜잭션으로 만들어 준다.
    그러므로, 해당 영역에서 예외가 발생할 경우 지금까지 수행한 데이터 액세스 처리를 모두 자동으로 롤백을 해주고,
    트랜잭션 처리가 끝날 경우, 모든 데이터 액세스 처리를 commit 하여 준다.


    - 클래스 레벨 예
@Service
@Transactional   //MemberService 클래스에 붙은 모습이다
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public Member createMember(Member member) {
        verifyExistsEmail(member.getEmail());

        return memberRepository.save(member);
    }
		
		...
		...
}

 

- 메소드 레벨 예

@RequiredArgsConstructor
@Service
public class MemberService {

	:
    
    @Transactional(readOnly = true) // 메소드 레벨에서 적용 모습
    public Member findVerifiedMember(long memberId) {
        Optional<Member> optionalMember =
                memberRepository.findById(memberId);
        Member findMember =
                optionalMember.orElseThrow(() ->
                        new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
        return findMember;
    }
    
    :
}

 

@Transactional 애노테이션이 붙어서 트랜잭션 처리가 되는 영역은 스프링 내부적으로 Proxy 기능을 사용한다.

 

 

+ JPA 로그 레벨 설정

아래의 설정을 추가해주면 콘솔창에서 트랜잭션의 시작과 종료 같은 로그를 확인할 수 있다.

 

- application.yml 파일 설정

spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create  # (1) 스키마 자동 생성
    show-sql: true      # (2) SQL 쿼리 출력
    properties:
      hibernate:
        format_sql: true  # (3) SQL pretty print
  sql:
    init:
      data-locations: classpath*:db/h2/data.sql
logging:
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG

위에는 파일 전체 내용이다.

 

로그 내용 출력을 위한 설정은

logging:
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG

위에 내용이다.

 

 

 

@Transactional을 붙인 각종 상황

 

  • 메소드에만 붙인 상황
    애노테이션이 붙은 메소드 영역 안에서 처리되는 데이터 엑세스 처리가 모두 한 트랜잭션으로 묶인다.
    해당 메소드에서 다른 메소드를 호출하게 되면 호출된 메소드 또한 트랜잭션의 영역안에 들어오게 된다.


  • 클래스에만 붙인 상황
    애노테이션이 붙은 클래스내의 모든 메소드에 @Transactional이 붙은 것과 같은 효과이다.


  • 클래스 레벨과 호출되는 메소드에 모두 붙은 상황
    메소드 레벨에 붙은 @Transactional이 적용된다.

@Transactional의 속성 종류

1. isolation

 

트랜잭션에서 일관성없는 데이터 허용 수준을 설정한다

 

2. propagation

 

트랜잭션 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정하는 옵션이다

 

- 속성값 종류

1. Propagation.REQUIRED

현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션에 참여하게 되고,
현재 진행중인 트랜잭션이 존재하지 않는다면 새로 트랜잭션을 생성한다.

 

 

3. noRollbackFor

 

특정 예외 발생 시 rollback하지 않는다.

 

4. rollbackFor

 

특정 예외 발생 시 rollback한다.

 

5. timeout

 

지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback 한다. (-1일 경우 timeout을 사용하지 않는다)

 

6. readOnly

 

트랜잭션을 읽기 전용으로 설정한다.

 

- 속성값 종류

1. true

해당 트랜잭션을 읽기전용 트랜잭션으로 만든다.

그 말이 무엇이냐면,

JpaRepository로 save, delete 같은 메소드를 호출하면 persist는 한다.

그로인해, "1차 캐시"에도 엔티티가 저장되고, "쓰기 지연 sql 저장소"에도 쿼리가 저장된다.

즉, 데이터 액세스 처리에 대해 "영속성 컨텍스트"에 내용이 담기게 된다.

그리고 그후 @Transactional의 영역을 벗어나면서 commit 도 하지만, commit 내부에서 flush를 하지 않는다.

그러므로, 결론적으로 DB에 반영이 되지않는다.

그리고 변경감지를 위한, 스냅샷 또한 생성하지 않는다.

그러므로, 데이터 수정이 필요하지 않은 조회 메소드에 대해서 성능 최적화를 위해 적용할 수 있겠다.

 

참조 : https://velog.io/@kdhyo/JavaTransactional-Annotation-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-26her30h

 

 

트랜잭션 전파(Transaction Propagation) 속성의 속성값

@Transactional의 속성 중 가장 많이 쓰이는 propagation 속성의 속성값에 대해 알아보자

참고로 propagation 유형의 디폴트값은 REQUIRED이다.

 

EX ) @Transactional(propagation = Propagation.REQUIRED)

 

  • Propagation.REQUIRED
    진행중인 트랜잭션이 없으면 생성하고 존재하다면 진행중인 트랜잭션에 참여한다.

  • Propagation.REQUIRES_NEW
    이미 진행중인 트랜잭션이 있더라도, 새로운 트랜잭션을 생성하여 작업을 처리한다.
    그리고 새로운 트랜잭션이 생성되어 종료될때까지 기존에 진행중이던 트랜잭션은 중지된 상태가 된다.

  • Propagation.MANDATORY
    Propagation.REQUIRED는 진행 중인 트랜잭션이 없으면 새로운 트랜잭션이 시작되는 반면, Propagation.MANDATORY는 진행 중인 트랜잭션이 없으면 예외를 발생시킨다.
    즉, 현재 트랜잭션을 시작하기 위해선 기존에 트랙잭션이 필수적으로 작동중인 상태여야 한다는것이다.

  • Propagation.*NOT_SUPPORTED*
    트랜잭션을 필요로 하지 않음을 의미한다. 진행 중인 트랜잭션이 있으면 메서드 실행이 종료될 때 까지 진행중인 트랜잭션은 중지되며, 메서드 실행이 종료되면 트랜잭션을 계속 진행한다.

  • Propagation.*NEVER*
    트랜잭션을 필요로 하지 않음을 의미하며, 진행 중인 트랜잭션이 존재할 경우에는 예외를 발생시킨다.

 

트랜잭션 격리 레벨(Isolation Level)

트랜잭션의 독립적으로 실행되어 격리성이 유지되기 위해 설정하는 속성으로,

다수의 클라이언트로부터 request가 들어와서 해당 request가 각각의 쓰레드에서 트랜잭션이 작동되어 처리 중

다수의 트랜잭션이 동일한 트랜잭션 영역을 진행할때 트랜잭션의 접근 허용 범위이다.

디폴트값은 Isolation.DEFAULT 이다.

  • Isolation.DEFAULT 데이터베이스에서 제공하는 기본 값입니다.
  • Isolation.READ_UNCOMMITTED 다른 트랜잭션에서 커밋하지 않은 데이터를 읽는 것을 허용합니다.
  • Isolation.READ_COMMITTED 다른 트랜잭션에 의해 커밋된 데이터를 읽는 것을 허용합니다.
  • Isolation.REPEATABLE_READ 트랜잭션 내에서 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 합니다.
  • Isolation.SERIALIZABLE 동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션이 수행되지 못하도록 합니다.

트랜잭션의 격리 레벨은 일반적으로 데이터베이스나 데이터소스에 설정된 격리 레벨을 따르는 것이 권장된다.

 

 

 

 

AOP 방식의 트랜잭션 적용

JTA를 이용한 분산 트랜잭션 적용 

위에 두가지 내용은 당장 필요한 내용은 아니므로 추후 정리하도록 하겠다.

 

 

 

 

 

트랜잭션 관련 참조 : 

https://junhyunny.github.io/spring-boot/transaction-in-spring-application-context-event/

 

Spring Application Context Event - 트랜잭션 처리

<br /><br />

junhyunny.github.io

https://daddyprogrammer.org/post/14625/spring-boot-events/

 

SpringBoot Events - 아빠프로그래머의 좌충우돌 개발하기!

이번 장에서는 SpringBoot framework 내에서 이벤트를 발행하고 사용하는 방법에 대해서 살펴보겠습니다. args) { SpringApplication.run(SpringEventsApplication.class, args); } } 이제 EventListener에 @Async를 적용하면 비

daddyprogrammer.org

 

excutorservice 관련 참조 :

https://yangbox.tistory.com/28

 

ExecutorService 사용법

Thread를 구현할 때 Runnable 인터페이스를 구현하거나 Thread 클래스를 상속하여 구현한다. 구현한 Thread를 new MyThread().start() 와 같이 호출하여 직접적으로 실행할 수도 있지만 기본 JDK의 java.util.conc..

yangbox.tistory.com

https://simyeju.tistory.com/m/119

 

Java] ExecutorService란?

❓ ExecutorService란? 병렬 작업 시 여러 개의 작업을 효율적으로 처리하기 위해 제공되는 JAVA 라이브러리이다. ❔ ExecutorService가 없었다면? 각기 다른 Thread를 생성해서 작업을 처리하고, 처리가 완

simyeju.tistory.com

https://passiflore.tistory.com/35