백엔드 개발/JPA

JPA - @Transactional 어노테이션 왜 쓰는걸까?....

dev.ojin 2024. 1. 23. 02:24

 

  • Transaction이란?

DB에서 상태변화의 한 주기를 뜻하는데, 난 이 설명보다 하나의 일처리를 안전하게 보장해주는 것으로 이해하는 게 더 쉽고 빨랐다.

 

우리가, 흔히 아는 rollback과 commit이 트랜잭션 제어 언어인 것이다.

모든 작업이 성공해서 DB에 정상 반영하는 것을 commit이라고 하고,

작업 중 하나라도 빠그라져서 다시 되돌리는 것을 rollback이라고 한다.

(마치 회귀물 드라마가 생각난다..!)

 

트랜잭션은 4가지를 꼭 보장해야만 하는데 이를 ACID라고 함

  • A : '원자성'(Atomicity) - 트랜잭션 안에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 아니면 모두 실패해야한다 ex. ATM에서 돈을 이체하거나 돈을 뽑을 때, 내가 돈을 입금하는 과정에 있어도, 잘못된 계좌이체에 돈을 입금하려고 하면 내 통장에서 금액이 출금도 안되고, 그 상대 계좌에서도 그 금액이 입금되어지지도 않는다. 이게 바로 원자성. 
  • C : '일관성'(Consistency) - 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다. ex 데이터베이스에서 정한 무결성 제약 조건을 항상 만족해야 함
  • I : '격리성'(Isolation) - 동시에 실행되는 트랜잭션들이 서로에게 영향을 주지 않도록 격리한다. 격리성은 동시성과 관련된 성능 이슈로 인해, 트랜잭션 격리 수준을 선택할 수 있다. 
더보기

트랜잭션의 격리성에 대해서 더 깊게 알아보자면, 격리 수준은 READ UNCOMMITED / READ COMMITED / REPEATABLE READ / SERIALIZABLE 이렇게 4가지이다.

READ UNCOMMITED - 다른 트랜잭션에서 커밋되지 않은 내용도 참조할 수 있음

 

 

  • D : '지속성'(Durability) - 트랜잭션을 성공적으로 끝내고 나면, 그 결과가 항상 기록되어야 한다. 그래야지 중간에 시스템에 문제가 발생해도, DB 로그를 추적해서 성공한 트랜잭션 내용으로 rollback을 할 수 있고, 그래야만 한다.

 

 

 

 

 

 

 

 

  • @Transactional 어노테이션

자바에서 제공하는 어노테이션 / 스프링 프레임워크에서 제공하는 어노테이션 총 2개가 있는데 우리는 스프링부트로 개발하고 있기 때문에 후자를 선택하는 것이 더 좋다.

왜나하면, 스프링 내에서 쓸 수 있는 옵션들이 매우 다양하기 때문이다.

 

 

@RunWith(SpringRunner.class)
@SpringBootTest
public class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;

    @Test
    @Transactional
    @Rollback(false)
    public void testMember() throws Exception {
        //given 준비, member 객체 -> 저장
        Member member = new Member();
        member.setUsername("member1");

        //when 실행, findMember = member 클래스 조회한 것
        Long savedId = memberRepository.save(member);
        Member findMember = memberRepository.find(savedId);

        //then 검증
        Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
        Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        Assertions.assertThat(findMember).isEqualTo(member); //회원 조회(findMember)와 회원 저장(member) 비교
        //비교 결과를 보여주는 syso
        System.out.println("findMember == member : " + (findMember = member));


    }


}

 

  • @Transactional 어노테이션이 적용되는 범위 : @Transactional 어노테이션이 메소드에 적용되면 해당 메소드의 실행이 하나의 트랜잭션 단위로 처리된다. 따라서 메소드 안에서 일어나는 모든 비즈니스 로직 - 모든 데이터베이스 컨트롤 - 은 하나의 트랜잭션 내에서 수행되고, 메소드가 성공적으로 완료가 되면 트랜잭션은 commit / 만일 예외가 발생하면 rollback 된다.

요약

  1. 메소드 실행 전에 트랜잭션이 시작함
  2. 메소드 내에서 일어나는 모든 데이터베이스 조작은 하나의 트랜잭션 내에서 수행됨
  3. 메소드 실행이 완료되면 트랜잭션이 커밋!
  4. 만약 메소드 실행 중에 예외가 발생하면 트랜잭션이 롤백!

그래서, 위의 코딩으로 다시 설명을 하자면 

testMember() 메소드 안에서 일어나는 

Long savedId = memberRepository.save(member);
Member findMember = memberRepository.find(savedId);

위의 두 줄의 로직은 한 트랜잭션 단위 안에 포함되고, 트랜잭션 내에서 처리됨.

이러한 방식으로 트랜잭션을 처리하면, 테스트가 실행된 후에 데이터베이스에 영향을 미치지 않도록 보통 rollback을 수행하지만

 

나는 또 다른 실험?을 위해 rollback 기능을 비활성화 하였기 때문에 데이터베이스가 저장이 되고 있다. -> @rollback(false)

 

@Transactional 어노테이션이 적용된 MemberRepositoryTest.Java 등 테스트파일에서 테이블을 생성해도 컬럼은 생성이 안되는 이유는?

테스트 클래스 안에서는 , 일반적으로 테스트가 잘 동작을 한다싶으면 테스트를 동작한 후에 rollback을 해버린다. 그래서 테이블 안에 데이터베이스가 텅 비어있는 것이다. (데이터가 실질적으로 들어가 있으면 반복적으로 테스트를 못하므로 이렇게 롤백을 하는 것임)

 

아니? 나는 그래도 데이터가 실제로 들어가있는 걸 보고싶다! 하면 @Rollback(false) 어노테이션을 추가하면 된다!