본문 바로가기
JPA

영속성 컨텍스트

by 혀눅짱 2023. 9. 13.

JPA의 기본중 가장 중요했던 개념인 것 같다. 이론적인 내용을 좋아하진 않지만 중요하기 때문에 간략히 정리해본다.

 

영속성 컨텍스트

 

JPA를 이해하는데 가장 중요한 용어이다.
엔티티를 영구 저장하는 환경이라는 뜻이다.
어플리케이션과 DB사이에서 객체를 보관하는 가상의 DB같은 역할을 한다.
서비스별로 하나의 EntityManager Factory가 존재하며 Entity Manager Factory에서 디비에 접근하는 트랜잭션이 생길 때 마다 쓰레드 별로 Entity Manager를 생성하여 영속성 컨텍스트에 접근한다!!
EntityManager에 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
영속성 컨텍스트는 EntityManager를 생성할 때 만들어지며 EntityManager를 통해 영속성 컨텍스트에 접근하고 관리한다.
다음 코드로 Entity를 영속성 컨텍스트에 저장할 수 있다.

 

@Repository
@RequiredArgsConstructor
public class MemberRepository {


    private final EntityManager em;

    public void save(Member member){
        em.persist(member);
    }

Spring에서는 EntityManager를 주입하여 사용하면 같은 트렌잭션의 범위에 있는 EntityManager는 같은 영속성 컨텍스트에 접근한다. 

EntityManager

EntityManager는 영속성 컨텍스트 내에서 Entity들을 관리하고 있다.
EntityManager는 JPA에서 제공하는 interface로 spring bean으로 등록되어 있어 Autowired로 사용할 수 있다.

 

Entity의 생명주기

비영속(new/transient)


  영속성 컨텍스트와 전혀 관계가 없는 상태이다.
  엔티티 객체를 생성하였지만 아직 영속성 컨텍스트에 저장하지 않은 상태를 의미한다.

Member member = Member.builder()
                        .name(form.getName())
                        .address(address)
                        .email(form.getEmail())
                        .pw(bCryptPasswordEncoder.encode(form.getPw()))
                        .memberRole(memberRole)
                                .build();

ex)멤버객체를 builder패턴을 통해 인스턴스만 만들어놓은상태

 

 

영속(managed)

  영속성 컨텍스트에 저장된 상태
  엔티티가 영속성 컨텍스트에 의해 관리된다.
  영속 상태가 되었다고 바로 DB에 값이 저장되지 않고 트렌젝션의 커밋 시점에 영속성 컨텍스트에 있는 정보들을 DB에 쿼리로 날리게 된다.

 

@Transactional
public Long join(Member member){
    validateDuplicateMember(member);
    memberDataJpaRepository.save(member);
    return member.getId();
}

ex)값 검증이 끝난 멤버객체를 save메서드로 영속화시킴

 

준영속(detached)


  영속성 컨텍스트에 저장되었다가 분리된 상태
  엔티티를 준영속 상태로 만드려면 entityManager.detach()를 호출한다.

 

삭제(removed)

 
  영속성 컨텍스트와 DB에서 해당 엔티티를 삭제하여 삭제된 상태

 

 

 

 

영속성 컨텍스트의 특징

 

1차 Cache


Save 메서드와 같이 DB변경하는 메서드를 실행하였을 때 바로 DB가 업데이트 되지 않고 영속성 컨텍스트 내부에 있는 캐시를 거쳐서 DB가 업데이트된다. 해당 캐시를 우리는 1차 캐시라고 부른다.
1차 캐시에는 영속 상태의 엔티티를 저장한다.
1차 cache는 Map의 형태로 만들어진다.
Map에는 key는 id값, value는 해당 entity값이 들어있다.
key값이 id라서 id로 조회를 하게 되면 영속성 context에 있는 1차 cache에 entity가 있는지 확인을 해보고 값이 있다면 DB조회없이 return한다. 만약 값이 없으면 쿼리문으로 조회를 하고 1차 cache에 저장후 return해준다.
id가 아닌 다른 값을 이용하여 조회를 하면 1차 cache가 적용되지 않는다.
Delete, Update와 같은 작업을 할 때도 JPA 내부적으로는 ID를 통한 조회를 많이 하게 된다.
즉, 하나의 Transactional에서 id값으로 조회하는 데이터들은 1차 cache에 저장을 하여 관리를 함으로써 JPA의 조회 성능이 올라간다.
1차 캐시에서 조회하는 방법

 

@Test
public void hyunwookTest() throws  Exception{
    Member member = new Member();
    member.setName("lee");
    memberRepository.save(member);
    member = memberRepository.findOne(member.getId());
    System.out.println("test>>>>>>>>>>>>>>>>"+member.getId());
}

 

ex)save(persist)를 통해 영속화시킨 멤버를 findone메소드로 탐색할경우 이미 영속성컨텍스트에 존재하므로 select 쿼리가 나가지 않는다.

 

 

동일성(Identity) 보장


영속성 콘텍스트는 영속 엔티티의 동일성을 보장한다.
※ 동일성은 값 뿐만 아니라 실제 인스턴스 자체가 같다는 뜻이다.
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 DB가 아닌 애플리케이션 차원에서 제공한다.

@Test
public void hyunwookTest() throws  Exception{
    Member member = new Member();
    member.setName("lee");
    memberRepository.save(member);
    Member member2 = memberRepository.findOne(member.getId());
    Member member3 = memberRepository.findOne(member.getId());
    boolean flag = member2 == member3;
   System.out.println("두객체가 같은가?>>>>" + flag );
}

true가 출력된다.

 

트랜잭션을 지원하는 쓰기 지연

entity값을 변경하면 DB에 바로 업데이트 하지 않는다.
트랜젝션 내부에서 영속 상태의 entity의 값을 변경하면 INSERT SQL Query들은 DB에 바로 보내지않고 쿼리 저장소에 쿼리문들을 생성해서 쌓아둔다.
쿼리 저장소에 쌓여있는 쿼리들은 entityManager의 flush()나 트렌젝션의 commit을 통해 보내지게 된다.
entityManager.flush();
flush()는 1차 캐시를 지우지 않고 쿼리를 DB에 날려서 DB와의 싱크를 맞추는 역할을 한다. 쿼리를 보내고 난 후에 commit()을 실행한다.
트렌젝션을 커밋하면 flush(), commit()을 하게 된다.

 

변경 감지(Dirty Checking)


엔티티의 수정이 일어나도 개발자는 영속성 컨텍스트에 따로 알려주지 않아도 영속성 컨텍스트가 알아서 변경 사항을 체크해준다. 이것을 Dirty checking이라고 한다.
1차 캐시에 entity를 저장할때 스냅샷 필드도 따로 저장하여 commit이나 flush를 할 때 해당 entity와 스냅샷을 비교하여 변경사항이 있으면 알아서 UPDATE SQL을 만들어서 DB에 전송한다.

 

플러시 (flush)


flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다. (영속성 컨텍스트를 비우는 것이 아님❗️)
1차 캐시를 지우지 않고 쿼리를 DB에 날려서 DB와의 싱크를 맞추는 역할(동기화)을 한다.
flush()를 하거나 트렌젝션의 commit을 하게 된다면 영속성 컨텍스트 내에 있는 쿼리저장소에 쌓여 있던 INSERT, UPDATE, DELETE SQL들이 데이터베이스에 날라간다.
flush를 실행하면 Dirty checking을 통해 스냅샷과 비교하여 수정된 entity를 찾고 UPDATE Query를 만들어 쿼리 저장소에 등록한 후 쿼리 저장소에 저장된 모든 쿼리를 DB에 보내어 동기화한다.

 

flush가 되는 기준

 

  1. em.flush()로 강제 플러시 시킬 때
  2. em.clear()로 영속성 컨텍스트 초기화할 때
  3. 트랜잭션 커밋 시점
  4. JPQL 실행 직전

'JPA' 카테고리의 다른 글

즉시로딩 과 지연로딩 그리고 N+1  (0) 2023.09.20
Entity 상속  (0) 2023.09.13
JPQL  (0) 2023.09.13
연관관계 설정  (0) 2023.09.11
JPA 기본문법 파악  (0) 2023.09.11