1. @Transactional
intelliJ에서 `shift` + `shift` 하고 검색창 띄워서
`SimpleJpaRepository.java` 검색해보자.
◆ SimpleJpaRepository
해당 클래스의 `save` 메서드 가볍게 살펴보면 다음과 같다.
@Override
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
// 새로운 엔티티는 영속성 컨텍스트에 저장(persist)
entityManager.persist(entity);
return entity;
} else {
// 이미 있는 거라면, 저장 혹은 수정(merge)
return entityManager.merge(entity);
}
}
모두 정상적으로 코드가 수행되면 `transaction`을 `commit`하고 아니면 예외 발생된다.
◆ 어노테이션
`@Transactional`
`@Transactional(readOnly = true)`
- readonly 옵션 - 읽기 작업에 대한 최적화를 수행할 수 있다. but `readOnly`를 걸어두고 수정작업을 하면 예외 발생된다.
- 클래스, 메소드에 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체 생성
- 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션을 시작하고 Commit or Rollback을 수행
- CheckedException or 예외가 없을 때는 Commit
- UncheckedException이 발생하면 Rollback
참고: https://imiyoungman.tistory.com/9
◆ 관련한 테스트코드 설명 및 annotation 설명
`@Transactional`을 테스트 해보는 겸, 테스트 관련 어노테이션도 아래에서 하나씩 설명해보도록 하겠다.
@SpringBootTest
public class TransactionTest {
@PersistenceContext
EntityManager em;
@Test
@Transactional
@Rollback(value = false)
@DisplayName("메모 생성 성공")
void test1() {
Memo memo = new Memo();
memo.setUsername("Robbert");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장
}
@Test
@Disabled
@DisplayName("메모 생성 실패") // @Transactional 안 달았을 때
void test2() {
Memo memo = new Memo();
memo.setUsername("Robbie");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장
}
}
• 어노테이션 설명
1. class에 `@SpringBootTest`를 붙여준다.
(내용 추가)
2. 스프링에서 자동으로 생성해준 EntityManager 주입받기(`@PersistenceContext`)
마치 `@Autowired`하듯이 붙여준다.
@PersistenceContext
EntityManager em;
3. Test하는 메서드에는 `@Test` 붙이기
4. Test가 여러개일 경우 어떤 역할을 하는 지 알기 위해 `@DisplayName()`으로 comment를 달아준다.
5. 테스트 코드에서 `@ Transactional `을 달아주고 테스트가 완료되면 Rollback해버리는데, 해당 롤백을 `@Rollback(value=false)`로 막아줄 수 있다.
6. `@Disabled`는 더이상 테스트하지 않겠다는 어노테이션이다.
7. 트랜잭션 설정을 하고 싶다면 `@Transactional`을 붙여주면 된다.
• 코드 내용 설명
`@ Transactional `을 붙였을 때와 안 붙였을 때와의 차이점이다.
붙이지 않고 돌리게 되면 아래의 에러가 발생한다.
No EntityManager with actual transaction available for current thread
- cannot reliably process 'persist' call
즉, 해당 어노테이션을 붙여야 트랜잭션이 유지되고 `EntityManger`도 `persist`를 정상적으로 수행할 수 있다.
2. 영속성 컨텍스트와 트랜잭션의 생명주기
스프링 컨테이너 환경에서는 영속성 컨텐스트와 트랜잭션의 생명주기가 일치한다.
즉 트랜잭션이 유지되는 동안 영속성 컨텍스트도 계속 유지된다고 생각하며 된다.
- 그동안 1차캐시, 쓰기지연저장소(ActionQueue), 변경감지(Dirty Checking)의 기능을 사용할 수 있음
Spring은 어떻게 Service부터 Repository 계층까지 모두 Transaction을 유지할 수 있는가?
=> 트랜잭션 전파 기능(Spring 제공)
`@Transactional` 어노테이션에서 전파의 기본 옵션은 `REQUIRED`이다.
해당 옵션은 부모 메서드에 트랜잭션이 존재하면 자식 메서드의 트랜잭션은 부모의 트랙잭션에 합류하게 된다. 따라서 만약 부모자식 모두 `@Transactioinal`을 걸어둔 후 실행을 돌리면 자식 메서드가 종료될 때 update가 실행(✖️)되는 것이 아니라 부모 메서드가 종료되고 트랜잭션이 커밋될 때 update가 실행(✔️)되는 방식인 것이다.
// 부모 메서드
@Autowired
MemoRepository memoRepository;
@Test
@Transactional
@Rollback(value = false)
@DisplayName("트랜잭션 전파 테스트")
void test3() {
memoRepository.createMemo(em);
System.out.println("테스트 test3 메서드 종료");
} // 이때 부모 커밋이 이뤄지고 transaction 끝 => ⭐ 즉 이때 update가 이미 되어버림
// 자식 메서드
@Transactional
public Memo createMemo(EntityManager em) {
Memo memo = em.find(Memo.class, 1);
memo.setUsername("Robbie");
memo.setContents("@Transactional 전파 테스트 중!");
System.out.println("createMemo 메서드 종료");
return memo;
}