JPA 동작 원리

HwangJerry·2023년 4월 10일
0

JPA에서 가장 중요한 2가지
(1) 객체와 관계형 데이터베이스 매핑; Object-Relational Mapping
(2) 영속성 컨텍스트

EntityManagerFactory와 EntityManager


웹 어플리케이션 내에서 EntityManagerFactory(이하 EMF)는 하나만 생성되어 어플리케이션 전체에서 공유됩니다. 그와 달리, EntityManger(이하 EM)는 데이터 관리 요청이 들어올 때 마다 EMF에 의해 생성되어 EMF가 생성하고 관리하는 커넥션 풀을 이용하여 DB와 상호작용하면서 데이터를 처리합니다.

EMF가 커넥션 풀을 생성하고 관리하는 과정은 다음과 같습니다.

  1. EMF가 생성될 때 데이터베이스 연결 정보를 설정합니다.
  2. EMF는 설정된 데이터베이스 연결 정보를 기반으로 커넥션 풀을 생성합니다. 커넥션 풀은 데이터베이스 연결을 미리 생성하고, 애플리케이션에서 사용할 때 마다 커넥션을 제공합니다.
  3. 어플리케이션에 EM을 생성할 때, EMF는 커넥션 풀에서 사용 가능한 커넥션을 가져와 EM을 생성합니다.
  4. EM은 커넥션 풀에서 가져온 커넥션을 사용하여 데이터베이스와 상호작용합니다.
  5. EM이 close()메소드를 호출하여 종료될 때, EMF는 해당 커넥션을 커넥션 풀에 반환합니다.

이렇게 JPA 구현체를 활용하면 EMF가 커넥션 풀을 생성하고 관리함으로써 개발자는 직접 커넥션 풀을 생성하고 관리할 필요가 없으므로 개발자는 데이터베이스 접속 관리의 부담을 줄일 수 있습니다.

영속성 컨텍스트


영속성 컨텍스트는 "엔티티를 영구 저장하는 환경"이라는 뜻입니다. JPA는 EntityManager를 활용하여 "영속성 컨텍스트"라는 엔티티를 관리하기 위한 환경을 제공합니다. 이는 개발자가 데이터베이스와 직접적으로 상호작용하는 부분을 최소화하고, 객체 지향적인 방식으로 데이터를 다룰 수 있도록 지원하기 위한 개념입니다.

EntityManager는 영속성 컴택스트를 생성하고, 엔티티를 데이터베이스에 저장하거나 읽어오는 등의 작업을 수행합니다. EntityManager는 일반적으로 트랜잭션 단위로 생성되며, 트랜잭션이 커밋되면 영속성 컨텍스트에 저장된 변경 내역이 데이터베이스에 자동으로 반영되는데, 이를 더티 체킹(Dirty Checking)이라고 합니다. 이는 영속성 컨텍스트 내에 관리되는 엔티티들의 스냅샷이 저장되기 때문이며, 만약 스냅샷과 현재 엔티티의 모습이 다를 경우 UPDATE SQL을 생성하여 이후에 SQL 쿼리를 한번에 보낼 때 해당 업데이트 쿼리도 같이 보내주면서 이루어집니다.

또한 영속성 컨텍스트는 일종의 캐시 역할을 수행하는데, 엔티티를 읽거나 저장할 때 메모리에 해당 엔티티를 저장하고 이를 관리합니다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

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

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
/* 
Q. em.update(member) 이런 코드가 있어야 하지 않을까?
--------------------------------------------------------------------
A. 아니다. JPA를 사용하면 마치 데이터를 자바 컬렉션 다루듯이 사용할 수 있다.
이는 JPA가 Dirty checking을 지원하기 때문이다.
*/

transaction.commit()

엔티티를 조회하는 상황을 가정하여 영속성 컨텍스트의 캐싱 기능에 대하여 이해해 봅시다. 엔티티를 조회하면, EntityManager는 우선 영속성 컨텍스트에 해당 엔티티가 존재하는지 확인합니다. 이 때 만약 그 엔티티가 영속성 컨텍스트에 있다면 데이터베이스에 접근하지 않고 바로 영속성 컨텍스트에서 엔티티를 가져오지만, 만약 없다면 데이터베이스에서 해당 엔티티를 가져온 뒤 그 엔티티를 영속성 컨텍스트에 저장합니다.

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 1차 캐시에 저장됨
em.persist(member1);

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

// 데이터베이스에서 조회 (DB에 해당 객체가 있다는 가정 하에)
Member findMember2 = em.find(Member.class, "member2");

// 영속성 컨텍스트의 동일성 보장
Member 1 = em.find(Member.class, "member1");
Member 2 = em.find(Member.class, "member1");
System.out.println(a == b); // 동일성 비교 : true

환경마다 달라지지만, 대표적으로 스프링 프레임워크 같은 컨테이너 환경에서는 EntityManager와 영속성 컨텍스트가 N:1로 이루어집니다. 영속성 컨텍스트에 담긴 엔티티의 생명 주기는 비영속(new/transient), 영속(managed), 준영속(detached), 삭제(removed) 로 나뉩니다. 엔티티가 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태일 경우 "비영속", 영속성 컨텍스트에 의해 관리되는 경우에 "영속", 영속성 컨텍스트에 저장되었다가 분리되면 "준영속", 삭제된 상태면 "삭제"가 됩니다.

// 객체 생성 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

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

// 객체 저장 (영속)
em.persist(member1);

// 객체 분리 (준영속)
em.detach(member1);

// 객체 다시 관리 (영속)
em.merge(member1);

// 객체 삭제
em.remove(member1);

영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것을 플러시라고 하며, .flush()를 통해 이루어집니다. 이는 트랜잭션 커밋 또는 JPQL 쿼리 실행 과정에서 자동적으로 호출되므로 직접 호출하는 경우는 잘 발생하지 않지만, 간혹 필요한 경우 수동으로 사용할 수 있습니다.


플러시를 이해할 때 주의할 점은, 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화시키는 작업만을 할 뿐, 영속성 컨텍스트를 비우지 않는다는 것입니다. 데이터를 처리할 때에는 트랜잭션이라는 작업 단위가 중요하므로 커밋 직전에만 동기화하면 되니 플러시를 직접 호출할 경우는 적습니다.

profile
알고리즘 풀이 아카이브

0개의 댓글