영속성 컨텍스트는 JPA에서 엔티티의 상태를 관리하기 위한 논리적인 작업 영역이다. 영속성 컨텍스트는 엔티티 매니저(Entity Manager)에 의해 생성되고 관리된다.
영속성 컨텍스트의 특징
- 1차 캐시: 영속성 컨텍스트에 의해 관리되는 엔티티 객체의 캐시. 반복적인 데이터베이스 조회를 줄이고, 성능을 향상시킬 수 있다.
- 지연 로딩: 연관된 엔티티를 실제로 사용할 때까지 로딩을 지연시키는 기능이다. 필요한 시점에 필요한 데이터만 로드하여 성능을 최적화할 수 있다. 프록시로 구현한다.
- 트랜잭션 관리: 영속성 컨텍스트에 저장된 엔티티는 트랜잭션이 커밋될 때까지 변경을 모아두고, 트랜잭션이 롤백되면 변경된 내용을 취소한다. 데이터 일관성을 유지할 수 있다.
- Dirty Checking: 엔티티의 상태 변화를 추적하여 변경된 속성만을 데이터베이스에 자동으로 반영한다.
영속성 컨텍스트의 구조
- 1차 캐시 : 엔티티 인스턴스들이 1차 캐시에 저장되어 관리된다. 1차 캐시 안에서 엔티티와 식별자 간의 매핑을 유지한다. 이를 통해 엔티티의 식별자를 사용하여 올바른 엔티티 인스턴스를 찾을 수 있다. 또한 애플리케이션에서 쓰기 작업이 일어난 경우 1차 캐시에 즉시 반영된다.
- 쓰기 지연 SQL 저장소: 트랜잭션이 커밋될 때까지 만들어둔 SQL을 모아두는 곳이다. 트랜잭션이 커밋되면 여기에 있는 변경 내용이 데이터베이스에 일괄적으로 반영된다.
애플리케이션에서 쓰기 동작이 발생했을 때 영속성 컨텍스트에서 일어나는 일
- 데이터가 변경되면 즉시 1차 캐시에 반영한다.
- 변경 사항이 지연 SQL 저장소에 저장된다.
- Transaction이 commit되면 Flush가 발생한다.
- 지연 SQL 저장소에 있는 SQL문을 DB에 요청한다.
1차 캐시 기능 확인하기
@RunWith(SpringRunner.class)
@SpringBootTest
class MemberRepositoryTest {
@Autowired MemberRepository memberRepository;
@Test @Transactional
public void testMember() throws Exception {
// given
Member member = new Member();
member.setUsername("A");
// when
Long savedId = memberRepository.save(member);
Member findMember = memberRepository.find(savedId);
// then
assertThat(findMember.getId()).isEqualTo(member.getId());
assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
assertThat(findMember).isEqualTo(member); // 같은 영속성 컨텍스트 안에서는 id가 같으면 같은 엔티티로 인식. 1차 캐시에서 찾아옴.
}
}
save 호출이 발생했을 때 member 엔티티가 1차 캐시에 저장되고, find로 조회하면 1차 캐시에서 엔티티를 조회한다. 그런데 테스트에서 @Transactional은 테스트가 끝나면 자동 롤백된다. 그래서 flush가 발생하지 않으므로, 테스트 과정에서 insert나 select는 발생하지 않는다.
flush가 발생하도록 하고 싶다면 테스트 메소드 위에 @Rollback(value = false)를 추가하면 된다. 테스트가 끝나고 트랜잭션이 커밋되므로 member가 insert된다. 단, 이때에도 save나find를 호출했을 때는 1차 캐시에서 작업이 일어나므로 즉시 sql이 실행되지는 않는다.