1. 영속성 컨텍스트
- 영속성 컨텍스트
- JPA가 내부에서 실제로 동작하는 방식
이 영속성 컨텍스트는 JPA를 하면서 계속 나오는 개념입니다. 확실하게 알고있어야합니다. 우선 JPA의 동작 방식을 알아보겠습니다.
EntityManagerFactory는 어플리케이션 시작시 한개만 만들어집니다. JPA는 사용자로부터 요청이 오면 EntityManagerFactory로 부터 EntityManager를 생성합니다. EntityManager는 내부적으로 데이터베이스 커넥션을 사용해서 DB를 사용합니다. 사용이 완료되면 연결을 끊어줘야합니다.
영속성 컨텍스트란?
- JPA를 이해하는 가장 중요한 용어
- "엔티티를 영구 저장하는 환경" 이라는 뜻
- 눈에 보이지 않음. 영속성 컨텍스트는 논리적인 개념
- EntityMager를 통해 영속성 컨텍스트에 접근
- "EntityMager.persist(entity);"
영속성 컨텍스트의 생명주기
- 비영속성(new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 멤버 객체를 생성한 상태 정도. jpa와 전혀 관계 없기 떄문에 비영속 상태임
Member member = new Member(0;
member.setId("member1");
member.setUsername("회원1");
- 영속(managed)
- 영속성 컨텍스트에 관리되는 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
멤버 객체를 생성하고 entityManager를 얻어와서 persist. 영속성 컨텍스트에 들어가면서 영속 상태가 됩니다. 이때 DB에 저장되는게 아닙니다. 트랜잭션을 커밋할 때 DB에 쿼리가 날라갑니다
- 준영속(detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
- 삭제(removed)
- 삭제된 상태
- 실제 데이터베이스에서 삭제를 요청하는 상태
//객체를 삭제한 상태
em.remove(member);
2. 영속성 컨텍스트의 이점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
애플리케이션과 데이터베이스 사이에 영속성 컨텍스트라는 중간 계층을 만든 것 입니다. 이렇게 중간 계층을 만들면 버퍼링을 할 수 있고, 캐싱을 할 수 있는 등의 이점이 있습니다.
3. 엔티티 조회 1차 캐시
//엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//엔티티를 영속
em.persist(member);
미묘한 차이가 있긴하지만, EntityManager 자체가 영속성 컨텍스트라고 생각해도됩니다. @Id가 있고 Entity가 있습니다. 키가 있고 Entity가 있는데 Entity가 값이고, 저장한 member 객체가 값이라고 생각하면 됩니다.
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
영속성 컨텍스트에서 1차캐시를 뒤집니다. 1차 캐시에서 member entity가 있으면 캐시에서 바로 조회해옵니다.
4. 데이터베이스에서 조회
Member findMember2 = em.find(Member.class, "member2");
member2를 조회 했습니다. 1차 캐시에 처음에 없으니까 JPA가 DB에서 조회한 member2를 1차 캐시에 저장합니다. 사실 이게 그렇게 큰 도움은 안됩니다. 고객이 요청이 하나 들어와서 비즈니스가 끝나면 영속성 컨텍스트를 지우면서 1차캐시도 지웁니다. 아주 짧은 순간에서만 의미가 있음. 이 1차 캐시는 데이터베이스 한 트랜잭션에서만 의미가 있기 때문에 성능상 큰 이점은 없습니다. 성능적 이점보다는 컨셉이 주는 이점이 있습니다.
이렇게 저장하면 영속 엔티티의 동일성을 보장해줍니다.
Member a = em.find(Member.class, "member1");
Member b= em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
마치 자바 컬렉션에서 꺼냈을때 동일 레퍼런스인 것 처럼 JPA가 영속 엔티티의 동일성을 보장해줍니다. 이게 가능한 이유가 1차 캐시가 있기 때문입니다.
5. 엔티티 등록
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
엔티티를 등록할 때 트랜잭션을 지원하는 쓰기 지연을 지원합니다. 트랜잭션을 실행하고 em.persist로 memberA, memberB를 넣어 두는데 여기까지 INSERT SQL을 DB에 보내지 않습니다. 쭉쭉 쌓고 있습니다. 이 트랜잭션을 커밋하는 순간 INSERT SQL을 데이터베이스에 보냅니다. 영속성 컨텍스트랑 트랜잭션의 주기를 맞춰서 설계를 들어가야 문제가없음. 그래야 데이터 동기화에 대한 이슈가 없습니다.
영속성 컨텍스트안에는 쓰기 지연 SQL 저장소라는게 또 있습니다. memberA를 넣으면 1차캐시에 들어가고 동시에 JPA가 이 entity를 분석해서 SQL을 생성해서 쓰기지연 SQL 저장소에 쌓아둡니다.
트랜잭션을 커밋하는 시점에 쓰기 지연 SQL에 있던 애들이 flush가 되면서 날라갑니다. 그리고 실제 데이터베이스 트랜잭션이 커밋됩니다. JPA는 동적으로 객체를 생성해내야 하기 때문에 기본 생성자가 하나 있어야합니다. 만약 바로 쿼리를 DB에 날리면 최적화할 여지가 없습니다. 이렇게 쌓인걸 DB에 한번에 보낼 수 있습니다. hibernate.jdbc.batch_size라는게 있는데 여기에 사이즈를 주면 모아서 한번에 네트워크로 보내고 db를 커밋칩니다. 버퍼링 같은 기능이 있는것이죠.
이런걸 잘 사용하면 성능을 향상 시킬 수 있습니다.
6.엔티티 수정 변경 감지
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(); // [트랜잭션] 커밋
꺼내와서 값 변경하고 다시 persist 하면 안됩니다. JPA는 변경 감지라는 기능이 제공됩니다.
JPA는 데이터베이스 트랜잭션을 커밋하면 내부적으로 flush가 호출됩니다. 엔티티와 스냅샷을 비교합니다. 스냅샷은 값을 읽어온 최초 상태를 1차 캐시에 넣어둔 것입니다. entity와 스냅샷을 비교해서 UPDATE SQL을 쓰지 지연 SQL 저장소에 담아둡니다. 그리고 이 업데이트 쿼리를 DB에 반영하고 커밋을 합니다.
7. 엔티티 삭제
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, “memberA");
em.remove(memberA); //엔티티 삭제
8. 플러시
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영합니다. 데이터베이스 커밋 일어날때 플러시가 일어나는데 쌓아놓은 SQL을 데이터베이스에 날라가는걸 말합니다.
[영속성 컨텍스트를 플러시하는 방법]
- em.flush() - 직접 호출
- 트랜잭션 커밋 - 플러시 자동 호출
- 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()
persist를 위에서 하고 밑에서 JPQL로 데이터를 읽어오려고하면 DB에서 꺼내와야하기 때문에 이때 flush를 호출합니다.
[플러시 모드 옵션]
em.setFlushMode(FlushModeType.COMMIT)
- FlushType.AUTO : 커밋이나 쿼리를 실행할 때 플러시(기본값)
- FlushType.COMMIT : 커밋할때만 플러시
AUTO로 하는걸권장합니다.(기본값)
플러시는 영속성 컨텍스트를 비우지 않습니다. 또한 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화하는 역할을 하며 트랜잭션이라는 작업 단위가 중요합니다. 즉, 커밋직전에만 동기화하면 됩니다.
9. 준영속 상태로 만드는 방법
- em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
- em.clear() : 영속성 컨텍스트를 완전히 초기화
- em.close() : 영속성 컨텍스틀 종료
REFERENCE
https://www.inflearn.com/course/ORM-JPA-Basic
'JPA' 카테고리의 다른 글
[JPA] 연관 관계 매핑의 종류 (0) | 2020.04.19 |
---|---|
[JPA] 연관관계 매핑 기초 (0) | 2020.04.14 |
[JPA] 기본 키 매핑 (0) | 2020.04.10 |
[JPA] 객체와 테이블 매핑 (0) | 2020.04.10 |
[JPA] 관계형 데이터베이스의 문제 (1) | 2020.04.08 |