이번 시간에는 엔터티와 JPA의 동작 방식에 대해 알아보겠습니다.
저번에도 엔터티의 개념에 대해 알아보았지만 CS의 키워드인 만큼 다시 한 번 다뤄보겠습니다.
정보를 저장하는 기본 단위이자 테이블로, 개발쪽에서는 시스템 내의 객체나
특정 의미를 가진 단어를 지칭하는 데 사용된다.
데이터 베이스 설계에 있어 데이터를 체계적으로 관리하고 로직을 구현하는데 핵심적인 역할이다.
현실의 객체나 개념을 DB 내에 표현한 것으로 아래 그림과 같이 보면 이해하기 수월할 것이다.
- 엔터티 : DB의 테이블
- 속성 : 엔터티가 가지는 속성값
- 인스턴스 : 엔터티에 저장된
Row들의 집합
| 분류 | 키워드 | 주요 예시 |
|---|---|---|
| 유형 | 물리적, 지속적 | 사원, 상품, 학생 |
| 개념 | 관리적, 추상적 | 부서, 학과, 보험상품 |
| 사건 | 업무수행, 통계적(기록) | 주문, 결제, 사고접수 |
| 분류 | 키워드 | 주요 특징 | 예시 |
|---|---|---|---|
| 기본 | 독립적, 부모 | 타 엔터티에 의존 X | 고객, 상품 |
| 중심 | 업무핵심, 가공 | 기본 엔터티들과 연관되어 비즈니스 로직 역할 수행 | 주문, 계약 |
| 행위 | 프로세스, 증식 | 중심 엔터티와 연결되어 이벤트 추적 등에 사용 | 주문상세, 입금내역 |
저번 게시글에서도 다뤘듯이 JPA는 자바 언어를 사용하여 DB와 관련된 작업을 더 쉽게 수행할 수 있도록 도와주는 기술이다. JPA를 사용하면 객체 지향 프로그래밍과 DB 간의 매핑을 간단하게 처리할 수 있다.
JPA는 ORM을 지원하며 ORM은 객체와 RDB 간의 변환 작업을 자동으로 처리해주는 기술이다.

엔터티를 영구 저장하는 환경으로
persist,Entity객체를 관리하는 영역
- EntityManager가 관리한다.
- Entity 객체를 저장하는 1차 캐시를 가지고 있다.
- 1차 캐시는 영속성 컨텍스트 내부에 존재하고 같은 엔터티를 여러번 조회하면 DB에 접근하지 않고 1차 캐시에서 데이터를 가져올 수 있다.
- EntityManager.persist(member) : 이 메서드가 호출되면 DB에 바로 저장되는 것이 아니라 영속성 컨텍스트에 저장되고 flush와 commit이 호출되면 DB에 반영된다.
persist(entity) : 주어진 엔터티 -> DB에 저장
merge(entity) : 주어진 엔터티 -> DB에 저장 / 업데이트
find(entityClass, primaryKey)
: 주어진 엔터티 클래스, 기본 키를 기반으로 엔터티 조회
remove(entity) : 주어진 엔터티 -> DB에서 삭제
createQuery(query, resultClass) : 주어진 JPQL 쿼리를 실행하여 결과 조회
이처럼 EntityManager를 생성하고 CRUD 작업 수행이 가능하다.
이제 자세한 동작 원리를 알아보자.

예를 들어
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA);
EntityManager.persist(member) 메서드를 사용한다고 바로 DB에 저장되는 것이 아니라 위의 그림처럼 영속성 컨텍스트 안에 있는 1차 캐시에 PK값을 KEY로 해서 저장이 되고 commit과 flush가 일어날 때 DB에 영구적으로 저장되고 영속성 컨텍스트는 소멸한다.

그리고 1차 캐시에 데이터가 있는 상태에서 데이터를 조회하면

위와 같이 DB에서 데이터를 가져오는 것이 아니라 1차 캐시에 있는 데이터를 조회해서 가져온다.
물론 1차 캐시를 확인한 뒤 조회할 데이터가 없다면 DB에서 조회하고
이를 다시 1차 캐시에 저장한다.
이를 확인하기 위해 테스트를 할 수 있는데
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
//영속 상태 => 영속성 컨텍스트가 MEMBER 객체를 관리해줌
System.out.println("===BEFORE===");
em.persist(member);
System.out.println("===AFTER===");
Member findMember = em.find(Member.class, 101L); // SELCET 쿼리가 안나가야한다.
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
System.out.println("=================================");
tx.commit();
코드의 결과는 아래와 같다.
다음 결과의 의미는 우선 em.persist를 BEFORE, AFTER의 중간에서 호출했음에도 쿼리문이 날라가지 않다가 트랜잭션이 커밋된
tx.commit(); 시점에서 데이터를 저장하는 insert 쿼리문이 나왔다.
그리고 또 하나의 기능을 살펴보면

insert 쿼리문이 들어오면 캐시에 값을 저장하면서 쓰기 지연 sql 저장소에
insert 쿼리문을 저장해주고 커밋할 때 한 번에 DB에 쿼리문을 뿌려준다.
Dirty Checking(변경감지)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();// JAP에서 데이터의 모든 작업은 transaction안에서 일어남
// 엔티티를 조회
Member memberA = em.find(Member.class, "memberA"); // => 영속 엔티티
//영속 엔티티를 수정
memberA.setname("hi");
tx.commit();
위와 같이 DB에서 데이터를 조회하고 값을 수정했다고 해보자.
하지만 persist 메서드를 사용해서 영속성 컨텍스트에 값을 보내지 않았다.
혹은 update 같은 메서드도 없다. 하지만 이 때도 값 변경이 일어난다.
이것이 변경감지의 기능인데 영속성 컨텍스트에서 값을 조회하면 해당 객체가 영속 엔터티의 성질을 가진다.
그리고 조회 시에는 1차 캐시에 저장이 됐을 것이고 1차 캐시에 저장될 때는 "스냅샷"이라는 것이 같이 저장되는데 1차 캐시에서 값을 읽어올 때 스냅샷을 떠둔다.
그러면 영속 엔터티의 값이 변경될 떼 데이터가 commit돼서 flush가 호출되는 시점에 스냅샷과 엔터티를 비교해서 변경사항을 쓰기 지연 sql 저장소에
update 쿼리를 날린다. 이후에 DB에 반영하는 매커니즘이다.
그림으로 보면 다음과 같다.

변경 감지에 대해선 다음에 더 알아보도록 하겠다.