JPA - @Transactional 어노테이션 왜 쓰는걸까?....
- 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 된다.
요약
- 메소드 실행 전에 트랜잭션이 시작함
- 메소드 내에서 일어나는 모든 데이터베이스 조작은 하나의 트랜잭션 내에서 수행됨
- 메소드 실행이 완료되면 트랜잭션이 커밋!
- 만약 메소드 실행 중에 예외가 발생하면 트랜잭션이 롤백!
그래서, 위의 코딩으로 다시 설명을 하자면
testMember() 메소드 안에서 일어나는
Long savedId = memberRepository.save(member);
Member findMember = memberRepository.find(savedId);
위의 두 줄의 로직은 한 트랜잭션 단위 안에 포함되고, 트랜잭션 내에서 처리됨.
이러한 방식으로 트랜잭션을 처리하면, 테스트가 실행된 후에 데이터베이스에 영향을 미치지 않도록 보통 rollback을 수행하지만
나는 또 다른 실험?을 위해 rollback 기능을 비활성화 하였기 때문에 데이터베이스가 저장이 되고 있다. -> @rollback(false)
@Transactional 어노테이션이 적용된 MemberRepositoryTest.Java 등 테스트파일에서 테이블을 생성해도 컬럼은 생성이 안되는 이유는?
테스트 클래스 안에서는 , 일반적으로 테스트가 잘 동작을 한다싶으면 테스트를 동작한 후에 rollback을 해버린다. 그래서 테이블 안에 데이터베이스가 텅 비어있는 것이다. (데이터가 실질적으로 들어가 있으면 반복적으로 테스트를 못하므로 이렇게 롤백을 하는 것임)
아니? 나는 그래도 데이터가 실제로 들어가있는 걸 보고싶다! 하면 @Rollback(false) 어노테이션을 추가하면 된다!