Entity란,
JPA에서 관리되는 클래스 즉, 객체를 의미합니다.
Entity 클래스는 DB의 테이블과 매핑되어 JPA에 의해 관리됩니다.
Entity는 이렇게 만들면 된다.
@Entity // JPA가 관리할 수 있는 Entity 클래스 지정
@Table(name = "memo") // 매핑할 테이블의 이름을 지정
public class Memo {
@Id
private Long id;
// nullable: null 허용 여부
// unique: 중복 허용 여부 (false 일때 중복 허용)
@Column(name = "username", nullable = false, unique = true)
private String username;
// length: 컬럼 길이 지정
@Column(name = "contents", nullable = false, length = 500)
private String contents;
}
영속성 컨텍스트
Persistence를 한글로 번역하면 영속성, 지속성 이라는 뜻이 됩니다.
Persistence를 객체의 관점으로 해석해 보자면 ‘객체가 생명(객체가 유지되는 시간)이나
공간(객체의 위치)을 자유롭게 유지하고 이동할수 있는 객체의 성질’을 의미합니다.
영속성 컨텍스트를 좀 더 쉽게 표현해 보자면 Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간입니다.
개발자들은 이제 직접 SQL을 작성하지 않아도 JPA를 사용하여 DB에 데이터를 저장하거나 조회할 수 있으며 수정, 삭제 또한 가능합니다.
이러한 일련의 과정을 효율적으로 처리하기 위해 JPA는 영속성 컨텍스트에 Entity 객체들을 저장하여 관리하면서 DB와 소통합니다.
EntityManager
영속성 컨텍스트에 접근하여 Entity 객체들을 조작하기 위해서는 EntityManager가 필요합니다.
EntityManager는 이름 그대로 Entity를 관리하는 관리자입니다.
개발자들은 EntityManager를 사용해서 Entity를 저장하고 조회하고 수정하고 삭제할 수 있습니다.
EntityManager는 EntityManagerFactory를 통해 생성하여 사용할 수 있습니다.
EntityManagerFactory는 일반적으로 DB 하나에 하나만 생성되어 애플리케이션이 동작하는 동안 사용됩니다.
JPA의 트랜잭션
JPA는 DB의 트랜잭션 개념을 사용하여 효율적으로 Entity를 관리하고 있습니다.
영속성 컨텍스트에 Entity 객체들을 저장했다고 해서 DB에 바로 반영 되지는 않습니다.
DB에서 하나의 트랜잭션에 여러 개의 SQL을 포함하고 있다가 마지막에 영구적으로 변경을 반영하는 것 처럼
JPA에서도 영속성 컨텍스트로 관리하고 있는 변경이 발생한 객체들의 정보를
쓰기 지연 저장소에 전부 가지고 있다가 마지막에 SQL을 한번에 DB에 요청해 변경을 반영합니다.
@Test
@DisplayName("EntityTransaction 성공 테스트")
void test1() {
EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.
et.begin(); // 트랜잭션을 시작합니다.
try { // DB 작업을 수행합니다.
Memo memo = new Memo(); // 저장할 Entity 객체를 생성합니다.
memo.setId(1L); // 식별자 값을 넣어줍니다.
memo.setUsername("Robbie");
memo.setContents("영속성 컨텍스트와 트랜잭션 이해하기");
em.persist(memo); // EntityManager 사용하여 memo 객체를 영속성 컨텍스트에 저장합니다.
et.commit(); // 오류가 발생하지 않고 정상적으로 수행되었다면 commit 을 호출합니다.
// commit 이 호출되면서 DB 에 수행한 DB 작업들이 반영됩니다.
} catch (Exception ex) {
ex.printStackTrace();
et.rollback(); // DB 작업 중 오류 발생 시 rollback 을 호출합니다.
} finally {
em.close(); // 사용한 EntityManager 를 종료합니다.
}
emf.close(); // 사용한 EntityManagerFactory 를 종료합니다.
}
이런식으로 테스트를 진행할 수도 있습니다.
영속성 컨텍스트는 Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간입니다.
영속성 컨텍스트가 어떻게 Entity 객체를 효율적으로 관리하고 있는지 살펴보겠습니다.
1차 캐시
영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있습니다.
우리가 저장하는 Entity 객체들이 1차 캐시 즉, 캐시 저장소에 저장된다고 생각하시면 됩니다.
캐시 저장소는 Map 자료구조 형태로 되어있습니다.
key에는 @Id로 매핑한 기본 키 즉, 식별자 값을 저장합니다.
value에는 해당 Entity 클래스의 객체를 저장합니다.
영속성 컨텍스트는 캐시 저장소 Key에 저장한 식별자값을 사용하여 Entity 객체를 구분하고 관리합니다.
Entity 저장은 아래와 같이 이루어집니다.
em.persist(memo); 메서드가 호출되면
memo Entity 객체를 캐시 저장소에 저장합니다.
Entity 조회는 아래와 같습니다.
캐시 저장소에 조회하는 Id가 존재하지 않은 경우
(만약에 값이 있다면 해당 Entity 객체를 바로 반환해주겠죠?)
em.find(Memo.class, 1); 호출 시 캐시 저장소를 확인 한 후 해당 값이 없다면?
DB에 SELECT 조회 후 해당 값을 캐시 저장소에 저장하고 반환합니다.
1차 캐시' 사용의 장점
DB 조회 횟수를 줄임
'1차 캐시' 를 사용해 DB row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)
// 같은 값을 조회하는 memo1과 memo2는 == 결과 true를 반환합니다.
// memo1과 다른 값을 조회하는 memo는 == 결과 false를 반환합니다.
Memo memo1 = em.find(Memo.class, 1);
Memo memo2 = em.find(Memo.class, 1);
Memo memo = em.find(Memo.class, 2);
Entity 삭제는 아래와 같습니다.
삭제할 Entity를 조회한 후 캐시 저장소에 없다면 DB에 조회해서 저장합니다.
em.remove(memo); 호출 시
삭제할 Entity를 DELETED 상태로 만든 후 트랜잭션 commit 후 Delete SQL이 DB에 요청됩니다.
쓰기 지연 저장소(ActionQueue)
JPA의 트랜잭션을 학습하면서 JPA가 트랜잭션 처럼 SQL을 모아서 한번에 DB에 반영한다는 것을 배웠습니다.
JPA는 이를 구현하기 위해 쓰기 지연 저장소를 만들어 SQL을 모아두고 있다가 트랜잭션 commit 후 한번에 DB에 반영합니다.
실제로 로그를 확인해보면 트랜잭션 commit 호출 전까지는 SQL 요청이 없다가
트랜잭션 commit 후 한번에 Insert SQL 2개가 순서대로 요청된 것을 확인할 수 있습니다.
flush()
트랜잭션 commit 후 쓰기 지연 저장소의 SQL들이 한번에 요청됨을 확인했습니다.
사실 트랜잭션 commit 후 추가적인 동작이 있는데 바로 em.flush(); 메서드의 호출입니다.
flush 메서드는 영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할을 수행합니다.
즉, 쓰기 지연 저장소의 SQL들을 DB에 요청하는 역할을 수행합니다.
추가) 트랜잭션을 설정하지 않고 플러시 메서드를 호출하면 no transaction is in progress 메시지와 함께 TransactionRequiredException 오류가 발생합니다.
Insert, Update, Delete 즉, 데이터 변경 SQL을 DB에 요청 및 반영하기 위해서는 트랜잭션이 필요합니다.
변경 감지(Dirty Checking)
영속성 컨텍스트에 저장된 Entity가 변경될 때마다 Update SQL이 쓰기 지연 저장소에 저장된다면?
하나의 Update SQL로 처리할 수 있는 상황을 여러번 Update SQL을 요청하게 되기 때문에 비효율적입니다.
그렇다면 JPA는 어떻게 Update를 처리할까요?
em.update(entity); 같은 메서드를 지원할 것 같지만 찾아볼 수 없습니다.
JPA에서는 Update를 어떻게 처리하는지 살펴보겠습니다.
JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장합니다.
트랜잭션이 commit되고 em.flush(); 가 호출되면
Entity의 현재 상태와 저장한 최초 상태를 비교합니다.
변경 내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고
모든 쓰기지연 저장소의 SQL을 DB에 요청합니다.
따라서 변경하고 싶은 데이터가 있다면 먼저 데이터를 조회하고
해당 Entity 객체의 데이터를 변경하면 자동으로 Update SQL이 생성되고 DB에 반영됩니다.
이러한 과정을 변경 감지, Dirty Checking이라 부릅니다.
트랜잭션 commit 후 em.flush(); 메서드가 호출되면 현재 상태와 최초 상태를 비교하고 변경이 있다면
Update SQL을 생성하여 쓰기 지연 저장소에 저장한 후 DB에 요청합니다.
Entity의 상태
비영속(Transient)
Memo memo = new Memo(); // 비영속 상태
memo.setId(1L);
memo.setUsername("Robbie");
memo.setContents("비영속과 영속 상태");
쉽게 말하자면 new 연산자를 통해 인스턴스화 된 Entity 객체를 의미합니다.
아직 영속성 컨텍스트에 저장되지 않았기 때문에 JPA의 관리를 받지 않습니다.
영속(Managed)
persist(entity) : 비영속 Entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태로 만듭니다.
비영속 상태는 JPA가 관리하지 못하기 때문에 해당 객체의 데이터를 변경해도 변경 감지가 이루어지지 않습니다!!
em.persist(memo); 메서드 호출 후 영속성 컨텍스트에 저장되었고
MANAGED 상태 즉, JPA가 관리하는 영속 상태의 Entity가 되었습니다.
준영속(Detached)
준영속 상태는 영속성 컨텍스트에 저장되어 관리되다가 분리된 상태를 의미합니다.
영속 상태에서 준영속 상태로 바꾸는 방법
detach(entity) : 특정 Entity만 준영속 상태로 전환합니다.
영속성 컨텍스트에서 관리되다(Managed)가 분리된 상태(Detached)로 전환됩니다.
준영속 상태로 전환되면 1차 캐시 즉, 캐시 저장소에서 제거되기 때문에
JPA의 관리를 받지 못해 영속성 컨텍스트의 어떠한 기능도 사용할 수 없습니다.
clear() : 영속성 컨텍스트를 완전히 초기화합니다.
영속성 컨텍스트의 모든 Entity를 준영속 상태로 전환합니다.
영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태가 됩니다.
따라서 계속해서 영속성 컨텍스트를 이용할 수 있습니다.
em.clear(); 메서드 호출 후 em.contains(memo1,2); 확인했을 때 false가 출력된 것을 확인할 수 있습니다.
다시 memo1 Entity를 조회한 후 em.contains(memo); 확인했을 때 true가 출력된 것을 확인할 수 있습니다.
또한 memo Entity 객체의 데이터를 수정하자 트랜잭션 commit 후 Update SQL이 수행된 것을 확인할 수 있습니다.
close() : 영속성 컨텍스트를 종료합니다.
해당 영속성 컨텍스트가 관리하던 영속성 상태의 Entity들은 모두 준영속 상태로 변경됩니다.
영속성 컨텍스트가 종료되었기 때문에 계속해서 영속성 컨텍스트를 사용할 수 없습니다.
em.close(); 메서드 호출 이후 EntityManager를 사용하려고 하자 오류가 발생했습니다.
영속성 컨텍스트가 종료되면 계속해서 영속성 컨텍스트를 사용할 수 없다는 것을 확인할 수 있습니다.
준영속 상태에서 다시 영속 상태로 바꾸는 방법
merge(entity) : 전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환합니다.
파라미터로 전달된 Entity의 식별자 값으로 영속성 컨텍스트를 조회합니다.
해당 Entity가 영속성 컨텍스트에 없다면?
DB에서 새롭게 조회합니다.
조회한 Entity를 영속성 컨텍스트에 저장합니다.
전달 받은 Entity의 값을 사용하여 병합합니다.
Update SQL이 수행됩니다. (수정)
만약 DB에서도 없다면 ?
새롭게 생성한 Entity를 영속성 컨텍스트에 저장합니다.
Insert SQL이 수행됩니다. (저장)
따라서 merge(entity) 메서드는 비영속, 준영속 모두 파라미터로 받을 수 있으며
상황에 따라 ‘저장’을 할 수도 ‘수정’을 할 수도 있습니다.
삭제(Removed)
remove(entity) : 삭제하기 위해 조회해온 영속 상태의 Entity를 파라미터로 전달받아 삭제 상태로 전환합니다.
'Back-End > JavaSpring' 카테고리의 다른 글
MSA에서 채팅 개발하기 (Spring, STOMP, Kafka, MongoDB, MySQL) - Kafka 세팅 #1 (0) | 2025.04.13 |
---|---|
QueryMethod, JPQL, QueryDSL 중에서 어떤것을 사용할까 (0) | 2025.04.10 |
Bean과 Spring IoC 컨테이너 (0) | 2025.03.31 |
JDBC가 등장한 이유와 JPA로 넘어간 이유 (3) | 2025.03.20 |
MSA에서 API gateway를 통한 인증/인가를 처리하는 방식 (0) | 2025.03.14 |