백엔드/Spring

[자바 ORM 표준 JPA 프로그래밍 - 기본편] 03. 영속성 관리

JYUN(sia) 2025. 2. 28. 17:51

영속성 컨텍스트 개념

데이터의 영속성이란, 데이터가 프로그램이나 프로세스의 실행이 종료되어도 사라지지 않고 지속적으로 보존되는 특성이다. JPA에서 영속성 컨텍스트는 "엔티티를 영구 저장하는 환경"이라는 뜻으로 데이터의 영속성을 관리하는 데 핵심적인 역할을 수행한다.

  • 영속성 컨텍스트는 논리적인 개념
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

엔티티의 생명주기

  • 비영속
    영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
  • 영속
    영속성 컨텍스트에 관리되는 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

//객체를 저장한 상태(영속)
em.persist(member);
  • 준영속
    영속성 컨텍스트에 저장되었다가 분리된 상태
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
  • 삭제
    삭제된 상태
//객체를 삭제한 상태(삭제)
em.remove(member);

 

 

개발자가 persist해서 집어 넣을 때도 영속 상태가 되지만 em.find나 JPA를 통해서 조회를 했을 때도 영속성 컨텍스트에 없으면 영속 상태로 전환된다.

영속성 컨텍스트 특징

1. DB 저장 시점

//비영속
Member member = new Member();
member.setId(1L);
member.setName("helloA");

//영속
em.persist(member);

tx.commit();

영속 상태는 entityManager안에 있는 영속성 컨텍스트를 통해서 member가 관리가 된다는 것을 의미한다.

 

그런데 em.persist(member) 시점에 DB에 저장되는 것은 아니다.

 

위 코드에서 볼 수 있는 것처럼 영속 상태가 된다고 바로 db에 쿼리가 날라가는 게 아니다.

트랜젝션을 커밋하는 시점, 즉 tx.commit()에 영속성 컨텍스트에 있는 코드가 DB에 쿼리를 보내게 된다.

 

2. 1차 캐시

//비영속
Member member = new Member();
member.setId("member1");
member.setName("회원1");

//영속, 1차 캐시에 저장됨
em.persist(member);

//1차 캐시에서 조회
Member findMember1 = em.find(Member.class, "member1");

em.find()를 하는 순간 먼저 1차 캐시에서 해당 데이터가 있는지 조회한 뒤, 있다면 반환하고 없다면 DB에 쿼리를 보내서 조회한다.

 

하지만 이는 여러명이 공유하는 캐시가 아니고 하나의 트랜젝션이 끝나면 (고객 한 명의 요청이 끝나면) 바로 지워버린다.

한 트랜젝션 안의 그 찰나의 순간에서만 1차 캐시가 유의미한 것이다.

 

Member findMember = em.find(Member.class, 1L);

위 코드로 member을 조회했지만 DB에 select쿼리가 생성되지 않은 걸 확인할 수 있다.

1차 캐시에서 조회된 데이터를 가져온 것을 의미한다.

 

3. 영속 엔티티의 동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a==b); //동일성 비교 true

같은 트랜잭션 내에서 같은 엔티티 ID로 조회된 엔티티 인스턴스는 항상 동일한 Java 객체임을 보장한다.

EntityManager가 관리하는 영속성 컨텍스트는 내부적으로 1차 캐시를 유지한다. 엔티티를 처음 조회할 때, 그 결과는 이 1차 캐시에 된다. 이후 같은 영속성 컨텍스트 내에서 동일한 엔티티 ID로 조회 요청이 발생하면 영속성 컨텍스트는 캐시된 엔티티를 반환하는 것이다.

 

4. 쓰기 지연

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); //[트랜잭션] 시작

em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); //[트랜잭션] 커밋

 

 

  • 트랜잭션 시작: EntityTransaction 객체를 사용하여 트랜잭션을 시작
  • 엔티티의 상태 변경: EntityManager의 persist, remove, merge 등의 메서드를 사용하여 엔티티의 상태를 변경한다. 이러한 변경은 즉시 데이터베이스에 반영되지 않는다.
  • SQL 명령의 쓰기 지연 저장소 저장: 변경된 엔티티에 대한 SQL 명령(예: INSERT, UPDATE, DELETE)은 영속성 컨텍스트 내의 쓰기 지연 저장소에 저장된다. 
  • 트랜잭션 커밋과 SQL 실행: 트랜잭션을 커밋할 때, 영속성 컨텍스트는 쓰기 지연 저장소에 쌓인 모든 SQL 명령을 데이터베이스로 한꺼번에 전송한다. 이는 데이터베이스의 입장에서는 모든 변경이 거의 동시에 발생한 것처럼 처리된다.
  • 데이터베이스 동기화: 데이터베이스는 받은 SQL 명령을 실행하여 실제 데이터에 변경 사항을 반영한다. 모든 SQL 명령이 성공적으로 처리되면 데이터베이스 트랜잭션도 커밋되며, 실패할 경우 롤백된다.

 

5. 변경 감지(더티 체킹)

JPA로 엔티티를 수정할 때는 단순히 엔티티를 조회에서 값을 수정하면 된다.

JPA의 초기 목적인 '객체를 컬렉션처럼'을 생각해보면 어느정도 납득된다.

 

작동원리

 

    • 엔티티의 스냅샷: 엔티티가 영속 상태로 전환될 때, 즉 EntityManager를 통해 처음 로드되거나 저장될 때 해당 엔티티의 초기 상태가 스냅샷으로 저장된다. 이 스냅샷은 엔티티의 각 필드 값을 포함하고 있으며, 영속성 컨텍스트 내부에 보관된다.
    • 트랜잭션 커밋 시 변경 감지: 트랜잭션이 커밋되는 시점에 영속성 컨텍스트는 모든 영속 상태의 엔티티를 검사한다. 이 때, 엔티티의 현재 상태와 저장된 스냅샷을 비교하여 어떤 필드가 변경되었는지 확인한다.
    • SQL 생성 및 실행: 변경이 감지된 엔티티에 대해 적절한 UPDATE SQL 문이 생성된다. 이 SQL은 데이터베이스에 자동으로 전송되어 엔티티의 최신 상태를 데이터베이스에 반영한다.

 

플러시

영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 과정을 말한다.

  • 변경 감지
  • 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송

영속성 컨텍스트를 플러시하는 방법

  • 트랜잭션 커밋 시: 트랜잭션이 커밋되는 순간, 영속성 컨텍스트를 자동으로 플러시하여 변경 내용을 데이터베이스에 반영한다.
  • 직접 호출 시: EntityManager.flush() 메소드를 호출하여 수동으로 플러시를 실행할 수 있다. 
  • 쿼리 실행 시: JPQL 쿼리를 실행할 때 JPA는 자동으로 플러시를 수행한다. 이는 쿼리 실행 전에 영속성 컨텍스트의 상태가 데이터베이스에 반영되어야 쿼리 결과가 정확하기 때문이다.