SpringBoot 환경에서의 JPA
- SpringBoot 환경에서는 EntityManagerFactory와 EntityManager를 자동으로 생성해줌
- 메모장 프로젝트 JPA 설정
- application.properties에 DB 정보를 전달해 주면 이를 토대로 EntityManagerFactory가 생성됨
@PersistenceContext
EntityManager em;
- @PersistenceContext 애너테이션을 사용하면 자동으로 생성된 EntityManager를 주입받아 사용 가능
Spring의 트랜잭션
- 트랜잭션: DB의 상태를 변화시키기 위해 하나의 논리적 기능을 수행하기 위한 작업의 단위
- 상황에 따라 여러개 만들어 질 수 있음
- A가 B에게 만 원을 송금한다고 가정
- A의 통장에서 만 원 인출
- B의 통장에 만 원 입금
= 인출(기능) + 입금(기능) ➡️ "송금"이라는 하나의 기능으로 됨
- 여러개의 쿼리가 한 번에 처리되는 상황에서 문제가 생기면 안되기 때문에 사용
- Spring 프레임워크에서는 DB의 트랜잭션 개념을 애플리케이션에 적용할 수 있도록 트랜잭션 관리자를 제공함
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
...
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
...
}
- 예시 코드 처럼 @Transactional 애너테이션을 클래스나 메서드에 추가하면 쉽게 트랜잭션 개념 적용 가능
- 메서드가 호출되면, 해당 메서드 내에서 수행되는 모든 DB 연산 내용은 하나의 트랜잭션으로 묶임
- 이때, 해당 메서드가 정상적으로 수행되면 트랜잭션을 커밋하고, 예외가 발생하면 롤백합니다.
- 클래스에 선언한 @Transactional 해당 클래스 내부의 모든 메서드에 트랜잭션 기능을 부여함
- 이때, save 메서드는 @Transactional 애너테이션이 추가되어있기 때문에 readOnly = true 옵션인 @Transactional을 덮어쓰게 되어 readOnly = false 옵션으로 적용됨
- readOnly = true 옵션
- 트랜잭션에서 데이터를 읽기만 할 때 사용
- 이 속성을 사용하면 읽기 작업에 대한 최적화 수행 가능
- 만약, 해당 트랜잭션에서 데이터를 수정하려고 하면 예외가 발생하기 때문에 주의해야함
@Transactional
@Test
@Transactional
@Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
@DisplayName("메모 생성 성공")
void test1() {
Memo memo = new Memo();
memo.setUsername("Robbert");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
@Test
@DisplayName("메모 생성 실패")
void test2() {
Memo memo = new Memo();
memo.setUsername("Robbie");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
- 트랜잭션이 적용되지 못해 작업이 취소됨
- 즉, JPA를 사용하여 DB에 데이터를 저장, 수정, 삭제 하려면 트랜잭션 적용이 반드시 필요
- 조회 작업은 단순하게 데이터를 읽기만 하기 때문에 트랜잭션 적용이 필수는 아님
- 다만 조회의 경우에도 트랜잭션 환경이 필요한 경우가 있을 수 있기 떄문에
- 조회 작업 기능만 존재하는 메서드일 경우 앞서 본 예시처럼 readOnly = true 옵션이 설정된 @Transactional을 적용하면 👍🏻
영속성 컨텍스트와 트랜잭션의 생명주기
- 스프링 컨테이너 환경에서는 영속성 컨텍스트와 트랜잭션의 생명주기가 일치
- 쉽게 설명하자면, 트랜잭션이 유지되는 동안은 영속성 컨텍스트도 계속 유지가 되기 때문에 영속성 컨텍스트의 기능 사용 가능
- 따라서 앞에서 작성한 테스트 코드 메소드에 트랜잭션이 적용되지 않았기 때문에 컨텍스트가 유지되지 못 해 오류가 발생
- Inser, Update, Delete 하려면 트랜잭션이 꼭 필요하다❗️
트랜잭션 전파
- @Transactional에서 트랜잭션 전파 옵션 지정 가능
- 트랜잭션 전파의 기본 옵션은 REQUIRED
- REQUIRED 옵션은 부모 메서드에 트랜잭션이 존재하면 자식 메서드의 트랜잭션은 부모의 트랜잭션에 합류
- createMemo : 자식 메서드
@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;
}
package com.sparta.memo;
import com.sparta.memo.entity.Memo;
import com.sparta.memo.repository.MemoRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
public class TransactionTest {
@PersistenceContext
EntityManager em;
@Autowired
MemoRepository memoRepository;
@Test
@Transactional
@Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
@DisplayName("메모 생성 성공")
void test1() {
Memo memo = new Memo();
memo.setUsername("Robbert");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
@Test
@Disabled
@DisplayName("메모 생성 실패")
void test2() {
Memo memo = new Memo();
memo.setUsername("Robbie");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
@Test
@Transactional
@Rollback(value = false)
@DisplayName("트랜잭션 전파 테스트")
void test3() {
memoRepository.createMemo(em);
System.out.println("테스트 test3 메서드 종료");
}
}
- 실행 결과 자식 메서드 createMemo가 종료될 때 update가 실행되는 것이 아니라 부모 메서드에 트랜잭션이 합류되면서 부모 메서드가 종료된 후 트랜잭션이 커밋될 때 update가 실행된 것을 확인 가능