JPA(Java Persistence API)에서 가장 중요한 두 가지 개념은 "객체와 관계형 데이터베이스 매핑하기"와 "영속성 컨텍스트"입니다. 이 두 개념은 JPA를 이해하고 효과적으로 사용하는 데 핵심적인 역할을 합니다.
1. 객체와 관계형 데이터베이스 매핑하기
객체-관계 매핑(ORM: Object-Relational Mapping)
객체와 관계형 데이터베이스 간의 매핑을 정의하는 과정입니다. 이를 통해 객체 지향 프로그래밍 언어의 객체를 데이터베이스 테이블에 저장하고, 데이터베이스 테이블의 데이터를 객체로 변환할 수 있습니다.
주요 매핑 어노테이션
- @Entity: 이 어노테이션을 클래스에 붙이면 JPA가 이 클래스를 엔티티로 인식합니다. 즉, 데이터베이스 테이블에 매핑되는 객체로 사용됩니다.
- @Table: 엔티티와 매핑할 테이블을 지정합니다. 생략하면 클래스 이름이 테이블 이름으로 사용됩니다.
- @Id: 엔티티의 기본 키를 매핑합니다.
- @GeneratedValue: 기본 키의 생성을 데이터베이스에 위임할 때 사용합니다.
- @Column: 필드를 데이터베이스의 컬럼에 매핑합니다. 생략하면 필드 이름이 컬럼 이름으로 사용됩니다.
- @OneToOne, @OneToMany, @ManyToOne, @ManyToMany: 엔티티 간의 관계를 매핑합니다.
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Column;
import javax.persistence.Table;
@Entity
@Table(name = "members")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String name;
// Getters and Setters
}
2. 영속성 컨텍스트
영속성 컨텍스트(Persistence Context)
영속성 컨텍스트는 엔티티를 영구 저장하는 환경을 의미합니다. JPA는 엔티티 매니저(EntityManager)를 통해 영속성 컨텍스트를 관리합니다. 영속성 컨텍스트는 엔티티의 생명 주기를 관리하고, 엔티티를 데이터베이스와 동기화하는 역할을 합니다.
주요 개념
- 영속 상태: 엔티티 매니저가 관리하는 엔티티 상태입니다. 영속성 컨텍스트에 의해 관리되며, 데이터베이스와 동기화됩니다.
- 비영속 상태: 영속성 컨텍스트가 관리하지 않는 엔티티 상태입니다.
- 준영속 상태: 한때 영속 상태였지만 현재는 영속성 컨텍스트가 관리하지 않는 엔티티 상태입니다.
- 삭제 상태: 영속성 컨텍스트가 관리하고 있지만, 삭제가 예약된 상태입니다.
주요 메서드
- persist(): 엔티티를 영속성 컨텍스트에 저장합니다.
- find(): 데이터베이스에서 엔티티를 조회하여 영속성 컨텍스트에 저장합니다.
- remove(): 엔티티를 영속성 컨텍스트에서 삭제합니다.
- detach(): 엔티티를 영속성 컨텍스트에서 분리합니다.
- merge(): 준영속 상태의 엔티티를 다시 영속성 컨텍스트에 병합합니다.
영속성 컨텍스트는 JPA의 핵심 개념으로, 엔티티 객체를 관리하고 데이터베이스와의 상호작용을 중재하는 역할을 합니다. 영속성 컨텍스트를 사용하면 여러 가지 이점을 누릴 수 있습니다. 아래는 주요 이점들에 대한 설명입니다:
1. 1차 캐시 및 동일성(identity) 보장
1차 캐시 (First-Level Cache):
- 영속성 컨텍스트는 1차 캐시를 사용하여 엔티티를 관리합니다.
- 동일한 엔티티가 여러 번 조회되면, 데이터베이스에서 다시 읽어오는 것이 아니라 1차 캐시에 저장된 객체를 반환합니다.
- 이는 성능을 향상시키고 데이터베이스 접근을 줄여줍니다.
동일성 보장:
- 동일한 트랜잭션 내에서 동일한 엔티티를 조회하면, 항상 같은 객체 인스턴스가 반환됩니다.
String memberId = "100";
Member m1 = em.find(Member.class, memberId);
Member m2 = em.find(Member.class, memberId);
System.out.println(m1 == m2); // true
2. 트랜잭션을 지원하는 쓰기 지연 (Transactional Write-Behind)
- 엔티티 매니저는 트랜잭션이 커밋될 때까지 INSERT, UPDATE, DELETE SQL 명령을 지연시킵니다.
- 이는 JDBC 배치 기능을 활용하여 여러 SQL 명령을 한 번에 전송함으로써 성능을 최적화합니다.
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// 실제 SQL 명령은 트랜잭션 커밋 시에 한 번에 전송됩니다.
tx.commit();
3. 변경 감지 (Dirty Checking)
- 영속성 컨텍스트는 엔티티의 변경 사항을 자동으로 감지하여 데이터베이스에 반영합니다.
- 엔티티 객체의 상태를 변경하면, 트랜잭션이 커밋될 때 변경된 내용을 자동으로 감지하고 필요한 SQL 업데이트를 수행합니다.
Member member = em.find(Member.class, 1L);
member.setName("Updated Name");
// 트랜잭션 커밋 시 변경 사항이 자동으로 감지되어 업데이트됩니다.
tx.commit();
4. 지연 로딩 (Lazy Loading)
- 연관된 엔티티를 실제로 사용할 때까지 로딩을 지연하여 성능을 최적화합니다.
- 이는 데이터베이스에 불필요한 쿼리 요청을 줄이는 데 도움이 됩니다.
Member member = em.find(Member.class, memberId);
Team team = member.getTeam(); // 여기서 Team 엔티티가 로딩됩니다.
플러시(Flush)는 영속성 컨텍스트에서 관리하는 엔티티의 변경 사항을 데이터베이스에 반영하는 중요한 작업입니다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스와 동기화하는 역할을 하며, 다음과 같은 상황에서 발생합니다:
플러시 발생 조건
1. em.flush() - 직접 호출
- 개발자가 EntityManager의 flush() 메서드를 직접 호출하여 플러시를 강제할 수 있습니다.
em.flush();
2. 트랜잭션 커밋 - 플러시 자동 호출
- 트랜잭션이 커밋될 때, 플러시는 자동으로 호출됩니다.
- 이는 트랜잭션이 끝나기 전에 모든 변경 사항이 데이터베이스에 반영되도록 보장합니다.
3. JPQL 쿼리 실행 - 플러시 자동 호출
- JPQL 쿼리가 실행되기 직전에 플러시가 자동으로 호출됩니다.
- 이는 JPQL 쿼리가 최신 상태의 데이터를 기반으로 실행되도록 하기 위함입니다.
Query query = em.createQuery("SELECT m FROM Member m WHERE m.name = :name");
query.setParameter("name", "John Doe");
List<Member> members = query.getResultList();
플러시 과정
플러시는 다음과 같은 단계를 거칩니다:
- 변경 감지
- 영속성 컨텍스트에서 관리하는 엔티티 중 변경된 엔티티를 감지합니다.
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 변경된 엔티티에 대한 SQL 쿼리(INSERT, UPDATE, DELETE)를 쓰기 지연 SQL 저장소에 등록합니다.
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송
- 쓰기 지연 SQL 저장소에 등록된 쿼리를 데이터베이스에 전송하여 실행합니다.
플러시의 특징
- 영속성 컨텍스트를 비우지 않음
- 플러시는 영속성 컨텍스트를 비우지 않습니다. 단지 변경된 내용을 데이터베이스에 반영할 뿐입니다.
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
- 플러시는 영속성 컨텍스트에 존재하는 엔티티의 변경 사항을 데이터베이스와 동기화합니다.
- 트랜잭션이라는 작업 단위가 중요
- 트랜잭션이란 작업 단위 내에서만 동기화를 수행하면 되기 때문에, 커밋 직전에 플러시가 발생하는 것이 일반적입니다. 이는 데이터 일관성을 유지하는 데 중요합니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
Member member = new Member();
member.setId(1L);
member.setName("John Doe");
em.persist(member);
// 직접 플러시 호출
em.flush();
// 커밋 시 플러시 자동 호출
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
영속성 컨텍스트를 비우는 작업은 엔티티를 준영속 상태로 만드는 것을 포함하며, 이를 통해 엔티티가 더 이상 영속성 컨텍스트에 의해 관리되지 않도록 할 수 있습니다. 다음은 준영속 상태로 만드는 주요 방법입니다:
em.detach(entity):
- 특정 엔티티만 영속성 컨텍스트에서 분리하여 준영속 상태로 만듭니다.
em.clear():
- 영속성 컨텍스트를 완전히 초기화하여 모든 엔티티를 준영속 상태로 만듭니다.
em.close():
- 영속성 컨텍스트를 종료하여 모든 엔티티를 준영속 상태로 만듭니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
Member member = new Member();
member.setId(1L);
member.setName("John Doe");
em.persist(member);
// 특정 엔티티만 준영속 상태로 전환
em.detach(member);
// member는 이제 준영속 상태
// 더 이상 영속성 컨텍스트에서 관리되지 않음
Member foundMember = em.find(Member.class, 1L); // null 반환
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
'JPA' 카테고리의 다른 글
[JPA] JPA 연관관계 매핑 (2) (0) | 2024.06.29 |
---|---|
[JPA] JPA 연관관계 매핑 (1) (0) | 2024.06.29 |
[JPA] JPA 엔티티 매핑 (0) | 2024.06.29 |
[JPA] JPA 생성 및 개발 (0) | 2024.06.29 |
[JPA] JPA 소개 (0) | 2024.06.29 |