해당 카테고리는 김영한님의 인프런 강의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'을 듣고 내용을 정리하기 위한 것으로 자세한 설명은 해당 강의를 통해 확인할 수 있습니다.
📝 영속성 컨텍스트
JPA에서 가장 중요한 2가지를 뽑으라면 다음과 같을 것이다.
- 객체와 관계형 데이터베이스 매핑하기(ORM: Object Relational Mapping)
- 영속성 컨텍스트
이번 시간에는 영속성 컨텍스트에 대해서 알아보도록 한다.
영속성 컨텍스트를 풀어 써보면 "엔티티를 영구 저장하는 환경"이라고 말할 수 있다.
영속성 컨텍스트는 논리적인 개념이며 눈에 보이지 않는다. 우리는 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있다.
엔티티에는 아래와 같은 생명주기가 존재한다.
- 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속(managed): 영속성 컨텍스트에 관리되는 상태
- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed): 삭제된 상태
📜 비영속 상태
비영속 상태란 객체를 생성만 한 상태로 영속성 컨테스트와 전혀 관계가 없는 상태를 말한다.
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
📜 영속 상태
영속 상태란 객체를 생성하고 저장한 상태로 영속성 컨텍스트에 의해 관리되는 상태이다.
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
📜 준영속 상태
준영속 상태란 영속성 컨텍스트에 저장되었다가 분리된 상태를 말한다.
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
//객체를 삭제한 상태(삭제)
em.remove(member);
준영속 상태의 엔티티는 당연히 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
준영속 상태로 만드는 방법은 다음과 같다.
- em.detach(entity): 특정 엔티티만 준영속 상태로 전환
- em.clear(): 영속성 컨텍스트를 완전히 초기화, 즉 모든 엔티티를 준영속 상태로 전환
- em.close(): 영속성 컨텍스트 삭제
📝 영속성 컨텍스트의 이점
영속성 컨텍스트를 사용하는 이유는 다음과 같다.
- 1차 캐시
- 동일성(identify) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
📜 엔티티 조회, 1차 캐시
우리가 객체를 저장하고 조회할 경우 해당 데이터를 DB에서 바로 찾아보는 것이 아니라 먼저 1차 캐시에서 조회하게 된다.
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
만약 1차 캐시에 존재하지 않는 데이터라면 DB에 접근하여 해당 데이터를 1차 캐시에 저장한 뒤, 그 값을 반환하게 된다.
📜 영속 엔티티의 동일성 보장
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
📜 트랜잭션을 지원하는 쓰기 지연
트랜잭션을 시작하고 아래와 같이 persist() 메서드를 통해 데이터를 DB에 저장한다고 하면, 내부적으로는 INSERT SQL을 데이터베이스에 날리게 될 것이다.
그런데 이때 메서드가 사용될 때마다 한 번에 한 번씩 SQL문이 전달되지 않고 해당 명령어들을 쓰기 지연 SQL 저장소에 저장해 놓은 뒤 커밋하는 순간 한 번에 데이터베이스에 모든 SQL문을 날리게 된다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
📜 변경 감지
객체를 수정하게 되면 개발자가 따로 업데이트 함수를 구현하고 호출하지 않아도 변경 감지(Dirty Checking)를 통해 자동으로 데이터베이스에 있는 데이터가 갱신된다. 객체 삭제도 마찬가지
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋
📝 플러시
플러시(flush)란 영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것을 말한다.
플러시가 발생하는 과정은 다음과 같다.
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 플러시하는 방법은 다음과 같다.
- em.flush(): 직접 호출
- 트랜잭션 커밋: 플러시 자동 호출
- JPQL 쿼리 실행: 플러시 자동 호출
📜 JPQL 쿼리 실행시 플러시가 자동으로 호출되는 경우
아래와 같이 객체를 영속화 시킨 직후 해당 객체와 관련된 JPQL을 실행하면 플러시가 자동 호출되어 변경내용이 반영된 데이터베이스의 데이터를 반환받을 수 있다.
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();
주의할 점은 플러시는 영속성 컨텍스트를 비우는 것이 아니라 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화시키는 것이며, 이러한 매커니즘이 가능한 이유는 트랜잭션이라는 작업 단위의 존재 때문이다. 즉, 커밋 직전에만 동기화하면 되기 때문에 가능한 것이다.
'🍃 Spring, Spring Boot > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 6. 프록시와 연관관계 관리 (0) | 2022.06.01 |
---|---|
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 5. 고급 매핑 (0) | 2022.05.31 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 4. 다양한 연관관계 매핑 (0) | 2022.05.31 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 3. 연관관계 매핑 기초 (0) | 2022.05.30 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 2. 엔티티 매핑 (0) | 2022.05.30 |