영속성 컨텍스트란, JPA의 생명주기를 관리하는 저장소
엔티티 매니저가 관리하는 영속성 컨텍스트 내부의 첫 번째 캐시
특정 연속성 컨텍스트 내에서만 유효
-> 엔티티 매니저 단위로 관리 -> 동일한 영속성 컨택스트 내에서만 유효
-> 동알한 영속성 컨텍스트 내에 조화된 엔티티는 모두 1차 저장소에 복귀
엔티티를 조회할 때, 영속성 컨텍스트는 먼저 1차 캐시 확인
엔티티의 식별자(Primary Key)를 기준으로 엔티티 인스턴스 저장
엔티티의 상태 변화를 추적
엔티티 매니저를 통해 생성되거나 조회된 엔티티는는 컨텍스트가 살아있는 동안 1차 캐시에 유지
엔티티 매니저가 종료되면 해당 영속성 컨텍스트와 1차 캐시가 사라짐
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
// 데이터베이스에서 엔티티를 조회합니다.
Member member = em.find(Member.class, 1L); // 첫 번째 조회, 데이터베이스에서 조회하여 1차 캐시에 저장
// 같은 엔티티를 다시 조회합니다.
Member sameMember = em.find(Member.class, 1L); // 두 번째 조회, 1차 캐시에서 조회
// 두 조회 결과가 동일한 인스턴스인지 확인합니다.
System.out.println(member == sameMember); // true
em.getTransaction().commit();
em.close();
em.find(Member.class, 1L) 호출이 두 번 이루어지지만, // 엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setName("회원1"); // 엔티티를 영속 -> 1차 캐시에 저장됨.
entityManager.persist(member);
// 1차 캐시에서 조회
Member member = entityManager.find(Member.class, "member1");
만약 1차 캐시에 존재하지 않는 엔티티를 찾고자 하는 경우에는?
반복 가능한 읽기 등급의 트래잭션 격리
Member member1 = entityManager.find(Member.class, "member1");
Member member2 = entityManager.find(Member.class, "member1"); System.out.println(member1 == member2); // 동일성 비교 결과 : true
영속성 컨텍스트는 엔티티를 조회할 때 먼저 1차 캐시를 확인
영속성 컨텍스트는 해당 엔티티를 1차 캐시에 저장
영속성 컨텍스트는 주어진 엔티티의 식별자 확인
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
// 엔티티를 데이터베이스에서 조회하여 1차 캐시에 저장
Member member1 = em.find(Member.class, 1L);
// 동일한 식별자를 가진 엔티티를 다시 조회
Member member2 = em.find(Member.class, 1L);
// 두 엔티티의 동일성 확인
System.out.println(member1 == member2); // true, 동일한 인스턴스
// 엔티티의 상태를 변경
member1.setName("Updated Name");
// 동일한 엔티티를 다시 조회하여 상태 확인
Member member3 = em.find(Member.class, 1L);
System.out.println(member3.getName()); // "Updated Name", 변경된 상태 반영
em.getTransaction().commit();
em.close();
member1, member2 는 동일한 식별자를 가짐 -> 동일한 인스턴스 반환
member1 상태를 변경하면 member3 으로 다시 조회 -> 동일한 인스턴스 반환 -> 변경 상태 반영
JPA에서 엔티티의 변경 사항을 즉시 데이터베이스에 반영하지 않고, 트랜잭션이 커밋될 때 한꺼번에 반영
-> 데이터베이스 접그 횟수 줄이고, 성능 최적화 가능
-> 변경 사항은 1차 캐시에 저장 -> 트랜잭션 커밋 시점에 데이터베이스에 반영
엔티티를 생성하고 영속성 컨텍스트에 추가 -> 데이터베이스에 실제로 저장 X
엔티티 상태가 변경되면, 변경 사항을 1차 캐시에 반영 -> 데이터베이스에는 반영 X
트랜잭션이 커밋되거나 flush 메서드가 호출되면 영속성 컨텍스트는 변경 사항을 데이터베이스 반영
-> 모든 엔티티에 대한 SQL 문이 실행되어 생성
플러시된 변경 사항이 확정되고 데이터베이스에 반영
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
// 새로운 엔티티를 생성하고 영속성 컨텍스트에 추가
Member member = new Member();
member.setName("John Doe");
em.persist(member);
// 엔티티의 상태를 변경
member.setName("Jane Doe");
// 아직 데이터베이스에 반영되지 않음
// 명시적으로 플러시를 호출하지 않으면, 트랜잭션 커밋 시점에 변경 사항이 반영됨
em.getTransaction().commit();
em.close();
em.persist(member) 호츨 -> 새로운 Member 엔티티가 영속성 컨텍스트에 추가 (데이터베이스에 저장 x)
엔티티의 이름을 변경해도 데이터베이스에 반영 X -> 1차 캐시에 저장
트랜잭션이 커밋되는 시점에서 Member엔티티의 생성과 변경 사항이 한꺼번에 데이터베이스에 반영
영속성 컨텍스트에 의해 관리되는 엔티티의 상태 변화를 자동으로 감지 -> 트랜잭션 커밋 시점에 해당 변경 사항을 데이터베이스에 반영
엔티티의 영속 컨텍스트에 처음 추가 -> 해당 엔티티의 원본 상태(snapshot) 저장
엔티티의 필드 값이 변경 -> JPA 엔티티의 현재 상태를 원본 상태와 비교해 어떤 필드가 변경됐는지 추적
트랜잭션이 커밋되거나 flush 메서드가 호출 -> JPA는 변경된 엔티티를 찾아 데이터베이스에 업데이트 쿼리를 생성하고 실행
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
// 엔티티를 데이터베이스에서 조회
Member member = em.find(Member.class, 1L);
// 엔티티의 필드 값을 변경
member.setName("Updated Name");
// 트랜잭션 커밋 시점에 변경 감지가 이루어짐
em.getTransaction().commit();
em.close();
member.setName() 메서드 호출 -> member 엔티티의 이름 필드 변경
트랜잭션이 커밋될 때 member 엔티티의 원본 상태와 현재 상태를 비교하여 변경 감지
JPA는 UPDATE 쿼리를 생성하여 변경된 이름을 데이터베이스에 반영
JPA에서 엔티티와 관련된 데이터를 실제로 필요할 때까지 로딩하지 않음
-> 불필요한 데이터를 가져오는 것 방지, 성능 최적화
-> 엔티티 간의 연관 관계를 처리할 때 사용
엔티티의 연관된 데이터를 접근할 때까지(사용할 때까지) 로딩 지연
@OnetoMany, @ManyToOne, @OneToOne, @ManyToMany 등의 연관 관계 설정
@ManyToOne (fetch = FetchType.LAZY) 처럼 설정을 하여 지연 로딩 지정
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
private List<Order> orders;
// getters and setters
}
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
// getters and setters
}
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
// Member 엔티티를 조회
Member member = em.find(Member.class, 1L);
// 지연 로딩 설정으로 인해 orders 컬렉션은 아직 로딩되지 않음
System.out.println("Member name: " + member.getName());
// orders 컬렉션에 접근하는 시점에서 데이터베이스 조회가 발생
List<Order> orders = member.getOrders();
for (Order order : orders) {
System.out.println("Order product: " + order.getProduct());
}
em.getTransaction().commit();
em.close();
member.getOrders()를 호출할 때서야 orders 컬렉션이 데이터베이스에서 로딩