영속성 컨텍스트
JPA가 관리하는 엔티티 객체의 집합으로 엔티티를 영구 저장하는 환경이라고 보면 된다. 애플리케이션을 실행하면 엔티티 매니저 팩토리가 생성되고 데이터베이스 작업을 위해 엔티티 매니저를 생성하면 엔티티 매니저에 안에 하나의 영속성 컨텍스트가 생성된다.
엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있다. 엔티티 매니저를 통해 엔티티 객체를 영속성 컨텍스트에 넣으면 JPA는 엔티티 객체의 매핑 정보를 가지고 데이터베이스에 반영한다.
엔티티 생명 주기
비영속성(new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태이다. 다르게 말하면 객체를 생성만 한 상태를 뜻한다. 그래서 영속성 컨텍스트에는 존재하지 않은 상태다.
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("홍길동");
영속(managed)
영속성 컨텍스트에 관리되는 상태이다. 영속성 컨텍스트에 객체가 들어 있는 상태를 말한다.
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("홍길동");
EntityManager em = emf.createEntityManger();
em.getTransaction().begin();
// 객체를 저장한 상태(영속)
em.persist(member);
준영속(detached)
영속성 컨텍스트에 저장되었다가 분리된 상태를 의미한다.
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("홍길동");
EntityManager em = emf.createEntityManger();
em.getTransaction().begin();
// 객체를 저장한 상태(영속)
em.persist(member);
// 회원 객체를 영속성 컨텍스트에서 분리한 상태(준영속)
em.detach(member);
삭제(removed)
영속성 컨텍스트에서 삭제된 상태를 의미한다.
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("홍길동");
EntityManager em = emf.createEntityManger();
em.getTransaction().begin();
// 객체를 저장한 상태(영속)
em.persist(member);
// 객체를 삭제한 상태(삭제)
em.remove(member);
영속성 컨텍스트의 장점
1차 캐시
영속성 컨텍스트 내부에 존재하는 캐시이다. 영속성 컨텍스트를 1차 캐시라고 해도 무방하다. 1차 캐시의 내부는 Key-Value로 이루어져 있으며 Key는 데이터베이스에서 Primary Key로 매핑되며, Value는 객체 자체가 된다.
아래 코드에서 Member 객체에 대한 데이터를 저장하고 em.persist(member)를 통해 member 객체를 영속상태로 즉 1차 캐시에 담게 된다. 그럼 다음과 같은 그림으로 나타낼 수 있다.
Member member = new Member();
member.setId("member1");
member.setUsername("gildong");
em.persist(member);
JPA에서 조회하면 1차 캐시에서 원하는 값을 찾아보고 있는 경우 캐시에 있는 값을 가져온다. 데이터베이스에서 쿼리를 실행하여 값을 가져오는 절차가 생략되는 것이다. 이점은 장점이라고 할 수 있지만, 엔티티 매니저는 트랜잭션 단위로 만들어지고 트랜잭션이 종료되면 지워지기 때문에 1차 캐시 또한 성능 측면에서 큰 이점은 없다. (애플리케이션 전체에서 공유하는 캐시는 하이버네이트에서 2차 캐시라고 한다.)
위 코드에서 member1 객체는 영속 상태이므로 1차 캐시에서 조회하여 값을 가져오게 된다.
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
아래의 코드를 추가해서 실행하면 member2 객체를 1차 캐시에서 조회하여 없는 것을 확인하고 데이터베이스에서 SQL문으로 조회하고, 조회된 데이터를 1차 캐시에 저장 후 findMember로 값을 반환한다.
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member2");
영속성 엔티티의 동일성 보장
JPA에서 데이터를 가져올 때 1차 캐시에서 가져오기 때문에 서로 다른 인스턴스임에도 동일하다는 것을 보장해준다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // 결과 : true
트랜잭션을 지원하는 쓰기 지연
em.persist(memberA);를 실행하면 INSERT 쿼리를 바로 데이터베이스로 보내서 처리하지 않고, 1차 캐시에 객체를 캐싱하고, 쓰기 지연 SQL 저장소에 처리할 쿼리를 담아놓는다. memberB도 동일하게 작업이 수행된다.
transaction.commit();이 실행되면 영속성 컨텍스트를 flush하여 쓰기 지연 SQL 저장소에 있는 모든 쿼리들을 데이터베이스로 보내서 처리하고 데이터베이스 commit까지 수행한다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(memberA);
em.persist(memberB);
// 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit();
변경 감지(Dirty Checking) - 엔티티 수정
1차 캐시에는 @Id와 Entity, Snapshot이 있다.
Snapshot은 초기에 값을 읽어온 엔티티 상태를 저장해둔다. flush() 했을 때 1차 캐시에 있는 엔티티와 스냅샷을 비교해서 변경 또는 삭제된 경우 UPDATE SQL 생성해서 쓰기 지연 SQL 저장소에 저장해둔 후 transcation.commit()가 수행되면 데이터베이스에 반영한다.
EntityManager = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("Jackson");
memberA.setAge(10);
transaction.commit();
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA);
'Programming > Java' 카테고리의 다른 글
[JAVA] 메모리 누수 확인 도구 (0) | 2024.12.29 |
---|---|
[JPA] 값 타입(Value Type) (0) | 2024.02.27 |
[JPA] 패러다임 불일치 - 우린 달라 달라 (0) | 2024.02.19 |
[JPA] JPA 시작 - SQL 잘 작성하는 개발자 (0) | 2024.02.17 |
[Java] 패키지 구조 (0) | 2023.12.20 |