이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


JPA 구동 방식

엔티티 매니저 팩토리 (EntityManagerFactory)

정의

  • EntityManagerFactory는 JPA 애플리케이션에서 EntityManager 인스턴스를 생성하기 위한 팩토리이다.

 

특징

  • 비용이 많이 드는 객체: 생성하는 데 많은 리소스를 사용하므로 애플리케이션 전체에서 한 번만 생성하고 공유하는 것이 일반적이다.
  • 애플리케이션 전체에서 공유: 여러 스레드에서 동시에 사용될 수 있다.
  • 생명 주기: 애플리케이션 시작 시 생성되고, 애플리케이션 종료 시 닫힌다.

 

 

엔티티 매니저 (EntityManager)

정의

  • EntityManager는 엔티티의 생명 주기(Life Cycle)를 관리하고, 데이터베이스 작업을 수행하는 객체이다.

 

특징

  • 데이터베이스 연결 관리: 데이터베이스와의 연결을 관리하고, 쿼리 실행, 트랜잭션 관리 등의 작업을 수행한다.
  • 영속성 컨텍스트: 엔티티 인스턴스들을 관리하는 영속성 컨텍스트(Persistence Context)를 제공한다. 영속성 컨텍스트는 엔티티의 상태 변화를 추적하고 데이터베이스와 동기화한다.
  • 스레드 안전하지 않음: EntityManager는 스레드에 안전하지 않으므로 각 트랜잭션 또는 요청마다 새로운 인스턴스를 생성하여 사용해야 한다.
  • 생명 주기: 일반적으로 짧은 생명 주기를 가지며, 각 트랜잭션마다 생성되고 종료된다.

 

주요 메서드

  • persist(Object entity): 엔티티를 영속성 컨텍스트에 저장한다.
  • merge(Object entity): 준영속 상태의 엔티티를 영속성 컨텍스트로 병합한다.
  • remove(Object entity): 엔티티를 영속성 컨텍스트에서 제거한다.
  • find(Class<T> entityClass, Object primaryKey): 기본 키로 엔티티를 조회한다.
  • createQuery(String qlString): JPQL 쿼리를 생성한다.
  • getTransaction(): 현재 트랜잭션을 반환한다.

 

객체와 테이블을 생성하고 매핑하기
create table Member (
	id bigint not null,
	name varchar(255),
	primary key (id)
);​

 

package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Member {
    @Id
    private Long id;
    private String name;
    
    //Getter, Setter …
}

@Entity 어노테이션은 해당 클래스가 JPA 엔티티임을 명시한다.

-클래스 선언부에 @Entity 어노테이션을 추가하여 해당 클래스가 엔티티임을 정의한다.
-엔티티 클래스는 반드시 @Entity 어노테이션을 가져야 하며, public 또는 protected로 선언된 기본 생성자가 있어야 한다.
-@Entity가 붙은 클래스는 반드시 @Id로 표시된 기본 키 필드를 가져야 한다.
-클래스 이름이 기본적으로 데이터베이스 테이블 이름으로 사용되지만, @Table 어노테이션을 사용하여 테이블 이름을 명시적으로 지정할 수 있다.


@Id 어노테이션은 엔티티의 기본 키를 정의하는 데 사용된다. 

-엔티티 클래스의 필드나 메서드에 @Id 어노테이션을 추가하여 해당 필드가 기본 키임을 정의한다.
-모든 JPA 엔티티는 하나 이상의 @Id 어노테이션을 가져야 하며, 이를 통해 기본 키를 지정해야 한다.
-기본 키의 값을 자동으로 생성하기 위해 @GeneratedValue 어노테이션과 함께 사용할 수 있다. @GeneratedValue는 기본 키 생성 전략을 지정하는 데 사용된다.

 

주의 사항

-엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유해야 한다.
- 엔티티 매니저는 쓰레드간에 공유를 하면 안된다. (공유를 하게되면 서비스의 장애를 일으킬 수 있다.)
- JPA의 모든 데이터 변경은 트랜잭션 안에서 실행된다.

 

영속성 컨텍스트

-"엔티티를 영구 저장하는 환경" 이라는 뜻

-EntityManager.persist(entity);

-영속성 컨텍스트는 논리적인 개념이다. 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있다.

 

스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1 의 관계이다.

 

엔티티의 생명주기

비영속 (Transient)

정의: 비영속 상태는 엔티티가 아직 영속성 컨텍스트에 의해 관리되지 않는 상태를 말한다.

 

특징

  • 데이터베이스와 전혀 관련이 없다.
  • 엔티티 매니저에 의해 관리되지 않는다.
  • 영속성 컨텍스트에 포함되지 않기 때문에 영속성 컨텍스트의 기능(변경 감지, 쓰기 지연 등)을 사용할 수 없다.
Member member = new Member();
member.setId(1L);
member.setName("Lee");

 

 

영속 (Persistent)

정의: 영속 상태는 엔티티가 영속성 컨텍스트에 의해 관리되는 상태를 말한다.

 

특징

  • 영속성 컨텍스트의 1차 캐시에 저장된다.
  • 트랜잭션을 커밋하거나 flush()를 호출하면 변경 사항이 데이터베이스에 반영된다.
  • 변경 감지(dirty checking) 기능이 적용되어 엔티티의 변경 사항이 자동으로 반영된다.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Member member = new Member();
member.setId(1L);
member.setName("Lee");
em.persist(member); // 엔티티를 영속성 컨텍스트에 저장

em.getTransaction().commit();

 

 

준영속 (Detached)

정의: 준영속 상태는 엔티티가 한 번 영속 상태였으나 현재는 영속성 컨텍스트에 의해 더 이상 관리되지 않는 상태를 말한다.

 

특징

  • 영속성 컨텍스트가 닫히거나, detach(), clear(), close() 메서드가 호출되면 엔티티가 준영속 상태로 전환된다.
  • 영속성 컨텍스트의 기능(변경 감지, 쓰기 지연 등)을 사용할 수 없다.

 

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

Member member = new Member();
member.setId(1L);
member.setName("Lee");
em.persist(member); // 엔티티를 영속성 컨텍스트에 저장
em.detach(member); // 엔티티를 준영속 상태로 전환

em.getTransaction().commit();
  • em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
  • em.clear() : 영속성 컨텍스트를 완전히 초기화
  • em.close() : 영속성 컨텍스트를 종료

 

삭제 (Removed)

정의: 삭제 상태는 엔티티가 영속성 컨텍스트와 데이터베이스에서 삭제될 예정인 상태를 말한다.

 

특징

  • remove() 메서드를 호출하면 엔티티가 삭제 상태로 전환된다.
  • 트랜잭션을 커밋하면 데이터베이스에서 해당 엔티티가 삭제된다.
  • 삭제된 엔티티는 더 이상 영속성 컨텍스트에서 관리되지 않는다.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Member member = em.find(Member.class, 1L);
em.remove(member); // 엔티티를 삭제 상태로 전환

em.getTransaction().commit(); // 트랜잭션을 커밋하여 데이터베이스에서 삭제

 

주요 메서드

  • persist(Object entity): 엔티티를 영속성 컨텍스트에 저장한다.
  • remove(Object entity): 엔티티를 영속성 컨텍스트에서 제거한다.
  • find(Class<T> entityClass, Object primaryKey): 기본 키를 통해 엔티티를 조회한다.
  • merge(Object entity): 준영속 상태의 엔티티를 영속성 컨텍스트로 병합한다.
  • flush(): 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
  • clear(): 영속성 컨텍스트를 초기화하여 모든 엔티티를 준영속 상태로 만든다.

 

 

영속성 컨텍스트의 이점

엔티티 조회, 1차 캐시

    • 영속성 컨텍스트는 엔티티를 1차 캐시에 저장하여 관리한다.
    • 같은 엔티티를 반복 조회할 때, 데이터베이스를 다시 조회하지 않고 1차 캐시에서 가져온다. 이를 통해 성능을 최적화할 수 있다.

 

 

  • 만약 조회시 1차 캐시에 존재하지 않는다면, DB에서 데이터를 가져오고 자동으로 영속 컨텍스트에 저장한다.

 

영속 엔티티의 동일성 보장

  • 영속성 컨텍스트는 같은 트랜잭션 내에서 같은 식별자를 가진 엔티티에 대해 동일한 객체를 반환한다. 이는 엔티티의 동일성을 보장하여 일관된 데이터를 제공한다.

 

쓰기 지연(Write-behind)

  • 영속성 컨텍스트는 엔티티의 변경 내용을 즉시 데이터베이스에 반영하지 않고, 트랜잭션을 커밋하거나 flush를 호출할 때까지 지연시킨다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작

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

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

 

변경 감지(Dirty Checking)

  • 영속성 컨텍스트는 엔티티의 상태 변화를 감지하고, 트랜잭션이 커밋될 때 변경된 내용을 자동으로 데이터베이스에 반영한다.
  • 이는 엔티티를 수정하고 flush 또는 commit 시 자동으로 UPDATE 쿼리를 생성하여 데이터베이스에 적용한다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

transaction.commit(); // [트랜잭션] 커밋

  • 최초로 1차 캐시에 저장될때, 스냅샷을 미리 생성하고 나중에 현재 스냅샷과 최초 스냅샷을 비교하여 변경점이 있으면 UPDATE SQL을 생성한다.

 

플러시

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
  • 플러시(Flush)는 JPA에서 영속성 컨텍스트(Persistence Context)에 있는 변경 사항을 데이터베이스에 동기화하는 작업이다.

 

특징

  • 영속성 컨텍스트에서 관리되는 엔티티의 변경 사항(INSERT, UPDATE, DELETE)을 SQL 문으로 데이터베이스에 보낸다.
  • 트랜잭션이 커밋되기 전에 자동으로 발생하지만, 명시적으로 flush() 메서드를 호출하여 중간에 실행할 수도 있다.
  • 일시적 동기화: 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하지만 트랜잭션을 종료하지는 않는다. 즉, 트랜잭션은 여전히 열려 있다.

 

플러시의 동작 시점

  • 트랜잭션 커밋: 트랜잭션을 커밋할 때 자동으로 플러시가 발생하여 변경 사항이 데이터베이스에 반영된다.
  • JPQL 쿼리 실행: JPQL 쿼리를 실행할 때 플러시가 발생하여 변경 사항이 쿼리 결과에 반영되도록 한다.
  • 명시적 호출: EntityManager의 flush() 메서드를 호출하여 명시적으로 플러시를 수행할 수 있다.

 

플러시 모드 옵션

em.setFlushMode(FlushModeType.COMMIT)
  • FlushModeType.AUTO : 커밋이나 쿼리(JPQL)를 실행할 때 플러시 (기본값)
  • FlushModeType.COMMIT : 커밋할 때만 플러시(JPQL을 작성할 때도 플러시 되지 않음)

 

주의 사항

  • 플러시는 영속성 컨텍스트를 비우지 않는다.
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화한다.