영속성 컨텍스트(PersistenceContext)
📌 Entity 객체를 영속성 상태로 관리하는 일종의 캐시 역할을 하는 공간으로 여기에 저장된 Entity는 데이터베이스와 자동으로 동기화되며 같은 트랜잭션 내에서는 동일한 객체가 유지된다.
- 영속성 상태 = JPA(Java Persistence API)에서 **엔티티(Entity)**가 EntityManager에 의해 관리되는 상태
- 논리적인 개념
- 눈에 보이지 않는 공간이 생긴다.
- Entity Manager 를 통해서 영속성 컨텍스트에 접근한다.
- EntityManager.persist(entity);
- Entity(객체)를 영속성 컨텍스트에 영속(저장)한다.
Entity
📌 데이터베이스의 테이블과 매핑되는 Java 클래스를 의미합니다.
- 데이터베이스에서 Entity 저장할 수 있는 데이터의 집합을 의미한다.
- JPA에서 Entity란 데이터베이스의 테이블을 나타내는 클래스를 의미한다.
1. Entity의 특징
- 데이터베이스 테이블과 매핑:
- Entity는 데이터베이스 테이블과 1:1로 매핑됩니다.
- Java 객체의 필드는 데이터베이스 테이블의 컬럼에 매핑됩니다.
- 필수 어노테이션:
- @Entity: 클래스를 Entity로 지정.
- @Id: 기본 키(Primary Key)로 사용할 필드 지정.
- 영속성 관리:
- Entity는 EntityManager에 의해 영속성 상태가 관리됩니다.
- POJO(Plain Old Java Object):
- Entity는 일반 Java 클래스처럼 동작하며, 특별한 상속이나 인터페이스 구현 없이 단순히 JPA의 규약을 따릅니다.
2. Entity 클래스 생성 예제
1) 기본 구조
import jakarta.persistence.*;
@Entity // 이 클래스를 Entity로 지정
@Table(name = "users") // 데이터베이스 테이블 이름 지정
public class User {
@Id // 기본 키 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 증가 값
private Long id;
@Column(nullable = false) // 필수 컬럼 지정
private String name;
@Column(unique = true) // 유니크 제약 조건
private String email;
// 기본 생성자
public User() {}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
3. 주요 어노테이션
1) @Entity
- 클래스를 JPA Entity로 선언.
- 필수 어노테이션.
@Entity
public class Product {
// 필드와 메서드 정의
}
2) @Table
- 매핑할 데이터베이스 테이블 이름을 지정.
- 생략 시 클래스 이름이 테이블 이름으로 사용.
@Table(name = "products")
3) @Id
- 기본 키(Primary Key)로 사용할 필드를 지정.
@Id
private Long id;
4) @GeneratedValue
- 기본 키의 값을 자동 생성.
- strategy 속성으로 생성 전략을 지정:
- IDENTITY: 데이터베이스가 자동 증가 값을 제공.
- SEQUENCE: JPA가 시퀀스를 사용해 기본 키 값 생성.
- TABLE: 키 생성 테이블 사용.
- AUTO: 데이터베이스에 맞는 전략을 자동 선택.
@GeneratedValue(strategy = GenerationType.IDENTITY)
5) @Column
- 테이블의 컬럼에 매핑.
- 속성:
- name: 컬럼 이름 지정.
- nullable: NULL 허용 여부.
- unique: 유니크 제약 조건.
- length: 문자열 길이 제한.
@Column(name = "user_name", nullable = false, length = 50)
6) @Transient
- 특정 필드를 데이터베이스에 매핑하지 않음.
@Transient
private String temporaryData;
4. Entity의 활용
1) 데이터 저장
persist() 메서드를 사용하여 데이터를 저장.
User user = new User();
user.setName("John");
user.setEmail("john@example.com");
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.persist(user); // INSERT 쿼리 실행
em.getTransaction().commit();
2) 데이터 조회
find() 메서드를 사용하여 기본 키를 기준으로 데이터를 조회.
User user = em.find(User.class, 1L); // SELECT 쿼리 실행
System.out.println(user.getName());
3) 데이터 수정
영속 상태의 엔티티를 변경하면 변경 사항이 자동으로 데이터베이스에 반영(Dirty Checking).
em.getTransaction().begin();
user.setName("Updated Name"); // Dirty Checking으로 UPDATE 실행
em.getTransaction().commit();
4) 데이터 삭제
remove() 메서드를 사용하여 엔티티를 삭제.
em.getTransaction().begin();
em.remove(user); // DELETE 쿼리 실행
em.getTransaction().commit();
5. Entity와 데이터베이스 간 매핑
1) 필드와 컬럼 매핑
- 엔티티 필드는 데이터베이스 컬럼에 매핑됩니다.
@Column(name = "user_email", nullable = false)
private String email;
2) 테이블 매핑
- 엔티티 클래스는 테이블과 매핑됩니다.
@Table(name = "users")
@Entity
public class User {
@Id
private Long id;
@Column(nullable = false)
private String name;
}
3) 관계 매핑
- JPA는 엔티티 간의 관계를 지원하며, 주요 관계 매핑 어노테이션:
- @OneToOne
- @OneToMany
- @ManyToOne
- @ManyToMany
예: @OneToMany 관계 매핑
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
6. Entity의 장점
- 객체 지향 데이터 모델링:
- 데이터베이스 테이블을 Java 객체로 매핑하여 객체 중심으로 설계 가능.
- SQL 자동 생성:
- Hibernate나 JPA가 적절한 SQL을 자동으로 생성.
- 데이터베이스 독립성:
- 엔티티를 작성할 때 데이터베이스에 의존하지 않음.
- 생산성 향상:
- 데이터 접근 코드 작성이 단순화되고 유지보수가 쉬움.
7. 주의사항
- 기본 생성자 필수:
- JPA는 리플렉션을 사용하므로 기본 생성자가 반드시 필요.
- Serializable 구현 권장:
- 엔티티는 종종 네트워크나 파일로 전달되므로 Serializable을 구현하는 것이 좋음.
- Equals와 HashCode 재정의:
- 엔티티를 컬렉션에서 사용할 경우, @Id 기반으로 equals()와 hashCode()를 재정의.
결론
Entity는 JPA의 핵심 요소로, 데이터베이스와의 상호작용을 객체 중심으로 단순화하고 효율적으로 설계할 수 있게 합니다. 올바른 매핑과 상태 관리를 통해 데이터베이스와 객체 간의 매끄러운 통신이 가능합니다. 추가적으로 관계 매핑이나 성능 최적화 방법에 대해 더 알고 싶다면 말씀해주세요!
Entity 생명주기
- 비영속(new/transient)
- 영속성 컨텍스트가 모르는 새로운 상태
- 데이터베이스와 전혀 연관이 없는 객체
- 영속(managed)
- 영속성 컨텍스트에 저장되고 관리되고 있는 상태
- 데이터베이스와 동기화되는 상태
- 준영속(detached)
- 영속성 컨텍스트에 저장되었다가 분리되어 더 이상 기억하지 않는 상태
- 삭제(removed)
- 영속성 컨텍스트에 의해 삭제로 표시된 상태
- 트랜잭션이 끝나면 데이터베이스에서 제거
엔티티의 생명주기를 현실 세계의 사람과 행정 시스템에 비유해서 설명해 보겠습니다.
1. 비영속 상태 (New/Transient)
- 비유:
- 새로 태어난 아기 같은 상태입니다.
- 아직 주민등록도 되어 있지 않고, 정부 시스템(행정 시스템)과 아무 연관이 없습니다.
- 단순히 메모리에만 존재하는 객체이며, 데이터베이스에 저장되지 않은 상태입니다.
- 예:
User user = new User(); // 비영속 상태 user.setName("John"); user.setEmail("john@example.com");
- User 객체는 만들어졌지만, 아직 데이터베이스와는 아무 관련이 없습니다.
2. 영속 상태 (Managed)
- 비유:
- 주민등록이 완료된 상태입니다.
- 행정 시스템(영속성 컨텍스트)에 등록되어 정부가 그 사람을 기억하고 관리합니다.
- 만약 사람이 이사(데이터 변경)를 하면, 시스템에도 자동으로 반영됩니다. (자동 동기화)
- 특징:
- 영속성 컨텍스트가 이 객체를 관리하며, 변경 사항이 자동으로 데이터베이스에 반영됩니다.
- 예:
- em.persist(user)를 호출하면 영속성 컨텍스트가 user를 관리하기 시작합니다.
- 이후 user.setName("Updated John")처럼 객체가 변경되면, 데이터베이스에도 자동으로 반영됩니다.
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
// 비영속
Tutor tutor = new Tutor(1L, "wonuk", 100);
System.out.println("persist 전");
// 영속
em.persist(tutor);
System.out.println("persist 후");
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}
em.persist() 가 호출되며 영속 상태가 된다.
- persist 후 출력 이후에 SQL이 실행된다.
- 트랜잭션 Commit 시점에 SQL이 실행된다.
3. 준영속 상태 (Detached)
- 비유:
- 주민등록이 말소된 상태입니다.
- 한때 행정 시스템(영속성 컨텍스트)에 등록되었지만, 더 이상 관리되지 않습니다.
- 이 사람의 정보가 행정 시스템과는 연관이 없으므로, 정보를 업데이트해도 정부 시스템에는 반영되지 않습니다.
- 특징:
- 객체는 메모리에 있지만, 영속성 컨텍스트와의 연결이 끊어져 데이터베이스와 동기화되지 않습니다.
- 예:
- detach() 메서드는 객체를 영속성 컨텍스트에서 분리하여 준영속 상태로 만듭니다.
- 객체를 변경해도 데이터베이스에 영향을 주지 않습니다.
- 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
- em.detach()
- 특정 Entity만 준영속 상태로 변경한다.
- em.clear()
- 영속성 컨텍스트를 초기화 한다.
- em.close()
- 영속성 컨텍스트를 종료한다.
4. 삭제 상태 (Removed)
- 비유:
- 사망신고를 한 상태입니다.
- 행정 시스템(영속성 컨텍스트)에 의해 삭제로 표시되었으며, 트랜잭션이 완료되면 데이터베이스(정부 기록)에서도 완전히 삭제됩니다.
- 특징:
- 삭제 상태로 표시되면, 트랜잭션이 완료되는 시점에 데이터베이스에서 삭제됩니다.
- 예:
- remove() 메서드를 호출하면, 해당 객체는 삭제 상태로 표시되고, 트랜잭션이 끝나면 데이터베이스에서 제거됩니다.
- em.remove(user); // 삭제 상태로 전환
5. 전체 흐름을 비유로 설명
- 비영속 상태: "아직 주민등록이 안 된 상태의 아기"
- 시스템(영속성 컨텍스트)과 전혀 연관이 없음.
- 영속 상태: "주민등록이 완료된 상태"
- 정부가 해당 사람을 등록하고 관리.
- 변경 사항(주소, 이름 등)이 자동으로 기록됨.
- 준영속 상태: "주민등록 말소 상태"
- 과거에 등록되었지만 더 이상 정부 시스템에서 관리하지 않음.
- 개인이 어디에 이사 가든 정부는 알 수 없음.
- 삭제 상태: "사망신고 상태"
- 정부 시스템에 더 이상 존재하지 않으며, 기록에서 삭제될 예정.
6. 실제 코드 흐름 예제
// 비영속 상태
User user = new User();
user.setName("John");
// 영속 상태로 전환
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.persist(user); // 영속 상태
user.setName("Updated John"); // 변경 감지(Dirty Checking)
// 준영속 상태로 전환
em.detach(user);
user.setName("Detached John"); // 데이터베이스에 반영되지 않음
// 삭제 상태로 전환
em.getTransaction().begin();
em.remove(user); // 삭제 상태
em.getTransaction().commit(); // 데이터베이스에서 제거
7. 요약
상태 | 비유 | 특징 |
비영속 | 주민등록이 안 된 상태 | 단순 객체 생성, 데이터베이스와 연관 없음. |
영속 | 주민등록 완료된 상태 | 영속성 컨텍스트가 관리하며 데이터베이스와 동기화됨. |
준영속 | 주민등록 말소된 상태 | 더 이상 영속성 컨텍스트가 관리하지 않으며 데이터베이스와 동기화되지 않음. |
삭제 | 사망신고된 상태 | 데이터베이스에서 삭제로 표시되며, 트랜잭션 완료 시 실제로 제거됨. |
1차 캐시
📌 엔티티를 영속성 컨텍스트에 저장할 때 생성되는 메모리 내 캐시이다. 엔티티는 먼저 1차 캐시에 저장되고 이후 같은 엔티티를 요청하면 DB를 조회하지 않고 1차 캐시에서 데이터를 반환하여 성능을 높일 수 있다.
- 영속성 컨텍스트의 1차 캐시에 저장된다.
// 비영속
Tutor tutor = new Tutor(1L, "wonuk", 100);
// 영속, 1차 캐시에 저장
em.persist(tutor);
- 영속된 Entity 조회
- Database가 아닌 1차 캐시에 저장된 Entity를 먼저 조회한다.
// 1차 캐시에서 조회
Tutor findTutor = em.find(Tutor.class, 1L);
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
// 비영속
Tutor tutor = new Tutor(1L, "wonuk", 100);
// 영속
System.out.println("persist 전");
em.persist(tutor);
System.out.println("persist 후");
Tutor findTutor = em.find(Tutor.class, 1L);
System.out.println("findTutor.getId() = " + findTutor.getId());
System.out.println("findTutor.getName() = " + findTutor.getName());
System.out.println("findTutor.getAge() = " + findTutor.getAge());
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}
- 1차 캐시의 Entity를 조회한다.
- 조회 SQL이 실행되지 않는다.
- 트랜잭션 Commit 시점에 INSERT SQL이 실행된다.
**트랜잭션(Transaction)**은 데이터베이스 작업에서 데이터의 일관성, 무결성을 보장하기 위해 논리적으로 묶여있는 작업 단위를 의미합니다. |
데이터베이스에 저장된 데이터 조회
- 1차 캐시는 동일한 트랜잭션 안에서만 사용이 가능하다.
- 요청이 들어오고 트랜잭션이 종료되면 영속성 컨텍스트는 삭제된다.
- 코드예시
- xml <property name="hibernate.hbm2ddl.auto" value="none" /> 설정
- DDL을 자동으로 생성하지 않는다.
- xml <property name="hibernate.hbm2ddl.auto" value="none" /> 설정
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
// 데이터베이스에서 조회 후 1차 캐시에 저장
Tutor findTutor = em.find(Tutor.class, 1L);
// 1차 캐시에서 조회
Tutor findCacheTutor = em.find(Tutor.class, 1L);
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
}
동일성 보장
📌 동일한 트랜잭션 안에서 특정 엔티티를 여러 번 조회해도 항상 같은 객체 인스턴스를 반환한다. 영속성 컨텍스트는 1차 캐시를 사용하여 같은 엔티티를 중복 조회해도 동일한 객체를 참조하게 하여 일관성을 유지한다.
- 동일한 트랜잭션 내에서 조회된 Entity는 같은 인스턴스를 반환한다.
- DB에 저장된 데이터를 조회하여 1차 캐시에 저장한다.
- 1차 캐시에 저장된 데이터를 조회한다.
- 코드예시
- Database에 저장된 데이터가 있는 상태
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
// DB 조회, 1차 캐시에 저장
Tutor findTutor1 = em.find(Tutor.class, 1L);
// 1차 캐시 조회
Tutor findTutor2 = em.find(Tutor.class, 1L);
System.out.println("findTutor1 == findTutor2 : " + findTutor1.equals(findTutor2));
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}
- 조회 SQL이 한번만 실행된다.
- 마치 Java Collection에서 객체를 조회하듯이 사용할 수 있다.
쓰기 지연
📌 엔티티 객체의 변경 사항을 DB에 바로 반영하지 않고 트랜잭션이 커밋될 때 한 번에 반영하는 방식으로 이를 통해 성능을 최적화하고 트랜잭션 내에서의 불필요한 DB 쓰기 작업을 최소화한다.
- 예시 코드
- xml <property name="hibernate.hbm2ddl.auto" value="create" /> 설정
- Entity와 매핑된 테이블을 삭제 후 새로 생성한다.
- 코드예시
- xml <property name="hibernate.hbm2ddl.auto" value="create" /> 설정
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
System.out.println("트랜잭션 시작");
Tutor tutor1 = new Tutor(1L, "wonuk1", 100);
Tutor tutor2 = new Tutor(2L, "wonuk2", 200);
em.persist(tutor1);
em.persist(tutor2);
System.out.println("트랜잭션 Commit 전");
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
System.out.println("트랜잭션 Commit 후");
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}
- 트랜잭션이 Commit된 이후에 SQL이 실행된다.
- 실행결과
- 여러개의 SQL을 하나씩 나누어 보낸다.
- hibernate.jdbc.batch_size
- 여러개의 SQL을 여러번 보내는 것이 아니라 합쳐서 하나로 보낸다.
- 통신 비용을 줄여서 성능을 개선할 수 있다.
변경 감지(Dirty Checking)
📌 영속성 컨텍스트가 엔티티의 초기 상태를 저장하고 트랜잭션 커밋 시점에 현재 상태와 비교해 변경 사항이 있는지 확인하는 기능이다.
- Database에 저장된 데이터가 있는 상태
- xml <property name="hibernate.hbm2ddl.auto" value="none" /> 설정
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
Tutor tutor = em.find(Tutor.class, 1L);
tutor.setName("수정된 이름");
// Java Collection을 사용하면 값을 수정하고 다시 저장하지 않는다.
// em.persist(tutor);
System.out.println("트랜잭션 Commit 전");
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
System.out.println("트랜잭션 Commit 후");
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}
- em.persist(tutor); 로 저장하지 않아도 update SQL이 실행된다.
- Entity를 변경하고자 할 때 em.persist() 를 사용하지 않아야 실수를 방지한다.
em.remove() 를 통해 Entity를 삭제할 때도 위와 같은 방식으로 동작한다. DELETE SQL이 트랜잭션 Commit 시점에 실행된다. |
flush
📌 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 기능으로, 변경된 엔티티 정보를 SQL로 변환해 데이터베이스에 동기화한다. 트랜잭션 커밋 시 자동으로 실행되지만 특정 시점에 데이터베이스 반영이 필요할 때 수동으로 호출할 수도 있다.
flush 사용 방법
- 자동 호출
- 트랜잭션이 Commit 되는 시점에 자동으로 호출된다.
- 수동 호출
- em.flush() 를 통해 수동으로 호출할 수 있다.
- 코드예시
- xml <property name="hibernate.hbm2ddl.auto" value="create" /> 설정
- 기존 테이블을 삭제(DROP) 후 다시 생성(CREATE)한다.
- xml <property name="hibernate.hbm2ddl.auto" value="create" /> 설정
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
Tutor tutor = new Tutor(1L, "wonuk", 100);
em.persist(tutor);
// flush 수동 호출
em.flush();
System.out.println("트랜잭션 Commit 전");
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}
'DB 접근 > JPA ( Java Persistence API )' 카테고리의 다른 글
[JPA] Spring Data JPA (0) | 2025.01.07 |
---|---|
[JPA] 연관관계 Mapping (0) | 2025.01.06 |
[JPA] 페러다임 불일치 문제 [객체 관계형 데이터베이스] (1) | 2025.01.02 |