엔티티 매니저 팩토리
엔티티 매니저를 통해서 웹 애플리케이션에서 고객 요청이 올 때마다 엔티티 매니저들을 생성한다.
엔티티 매니저들은 내부적으로 데이터 커넥션 풀을 사용해서 디비를 사용한다.
영속성 컨텍스트란 무엇일까?
엔티티 영구 저장 환경
→ JPA를 이해하는데 가장 중요한 용어
entityManager.persist(entity);
는 DB에 저장하는 게 아니라, 엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트라는 곳에 저장한다는 뜻이다.
영속성 컨텍스트의 개념
영속성 컨텍스트는 눈에 보이지 않는 논리적인 개념이다.
엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.
엔티티 매니저 생성 → 영속성 컨텍스트 생성
→ 1:1 로 생성이 된다.
엔티티의 생명주기
1. 비영속 (new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
Member member = new Member();
member.setId("memberId");
member.setUsername("회원1);
그냥 객체만 생성했을 때는 비영속이다. JPA와 전혀 관계가 없는 상태이다.
2. 영속 (managed)
영속성 컨텍스트에 관리되는 상태
Member member = new Member();
member.setId("memberId");
member.setUsername("회원1);
//위의 비영속상태의 객체를
EntityManager em = emf.createEntityManager(); //엔티티 매니저를 생성해
**em.persist();** //영속상태가 됨.
주의할점
- em.persist()를 해서 DB에 저장되는 것은 아니다.
- 트랜잭션을 커밋하는 시점에 영속성 컨텍스트에 있는 객체가 DB에 저장되게 된다.
3. 준영속 (detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
Member member = new Member();
member.setId("memberId");
member.setUsername("회원1);
//위의 비영속상태의 객체를
EntityManager em = emf.createEntityManager(); //엔티티 매니저를 생성해
em.persist(); //영속상태가 됨.
**em.detached();** //다시 비영속 상태가 된다.
4. 삭제(removed)
삭제된 상태
왜 영속성 컨텍스트를 사용할까?
영속성 컨텍스트는 DB와 서비스 로직 사이에 있는 것이라고 보면 된다. 왜 굳이 중간에 둘까?
- 1차 캐시
- 동일성을 보장해준다.
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
이런 장점들이 있다.
1. 엔티티 조회, 1차 캐시
Member member = new Member();
member.setId("memberId");
member.setUsername("회원1);
//위의 비영속상태의 객체를
EntityManager em = emf.createEntityManager(); //엔티티 매니저를 생성해
em.persist(); //영속상태가 됨.
em.persist()를 하면 영속성 컨텍스트에 저장되게 되는데,
em.persist(member); // 1차 캐시에 저장되게 된다.
그러면 em.find를 했을 때 1차 캐시에서 조회하게 된다.
em.find(Member.class,"member1");
만약, 1차 캐시에 없는 member2를 조회하게 된다면?
- member2가 1차 캐시에 없는 것 확인
- DB에서 조회
- 1차 캐시에 저장
- 반환
→ 이렇게 다음에 찾을 때도 1차 캐시에서 반환하게 된다.
이런게 큰 이득이라고 보기에는 어렵다. 결국 영속성 컨텍스트를 닫게 된다면 1차 캐시는 바로 지워지기 때문! 따라서 한 트랜잭션 내에서만 유효하다.
하지만 비즈니스 로직이 굉장히 복잡할 때는 이득일 수도 있겠다.
예시
Member member1 = em.find(Member.class,10L);
member1.getId();
member1.getName();
이렇게 해서 실행시키게 되면 분명 find 라는 조회를 했는데, select 쿼리가 나가지 않는다.
1차 캐시에서 가져오기 때문이다.
2. 엔티티의 동일성 보장
Member findMember1 = em.find(Member.class,10L);
Member findMember2 = em.find(Member.class,10L);
//다른 객체인데, 같다고 나온다.
findMember1==findMember2 -> **true**
1차 캐시로 반복 가능한 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공해준다.
트랜잭션을 지원하는 쓰기 지연
em.persist(memberA);
em.persist(memberB); //이때 데이터베이스에 보내는게 아니라
trasction.commit(); //이때 보내짐.
- 멤버 A를 저장하면 → 1차 캐시에 들어가고, 그 동시에 JPA가 그 엔티티를 분석해 insert SQL를 생성한다. → 그것이 쓰기지연 SQL 저장소에 들어간다.
- 멤버 B를 저장하면 똑같이 1차 캐시에 들어가고, 동시에 JPA가 insert SQL를 생성해 그게 쓰기지연 SQL 저장소에 들어간다.
JPA 는 기본적으로 리플랙션을 쓰므로 동적 객체 생성을 해야 한다. → 기본 생성자가 필요. 따라서 기본 생성자를 정의해주지 않으면 에러가 난다.
어쨌든 아무리 열심히 쿼리를 써도 commit을 하지 않으면 DB에 들어가지 않는데, 한 번에 데이터를 보낼 수 있는 방법이 있음 → JDBC 배치
- 하이버네이트는 배치사이즈라는 옵션이 있는데, 그것을 이용하면 데이터베이스에 한방에 보냄.
- 버퍼링 같은 개념이다.
3. 더티 체킹이 가능
Member meber = em.find(Member.class); //select 쿼리
member.setName("B"); //update 쿼리
commit을 하지 않아도 update 쿼리가 나가면서 데이터베이스에 적용이 된다.
→ JPA가 자동적으로 변경 감지를 하기 때문이다.
em.update()가 없어도 되는 이유? → 영속성 컨텍스트 안에 다 있음.
더티 체킹 순서
순서: 데이터 트랜잭션 커밋 → flush()가 됨 → 엔티티랑 스냅샷을 비교함.
스냅샷: 내가 값을 처음으로 읽어온 시점
이런 상태에서 내가 멤버의 값을 변경했을 때, JPA 트랜잭션이 커밋 되는 시점에 JPA가 다 비교해서 멤버 A의 변경을 감지한다.
업데이트 문을 그래도 써야할까?
영한님: 안쓰는게 정답이다
if(member.getName().equal("ZZZZ"){
em.update(member);
} //틀린코드
JPA는 어차피 이렇게 코드를 짜도 변경되는 시점에 업데이트를 쓰기 때문에! 필요 없다 ㄱ-
4. 엔티티 삭제 기능
Member memberA = em.find(Member.class,1L);
em.remove(memberA);
플러시
플러시는 영속성 컨텍스트의 내용을 데이터 베이스에 반영하는 작업을 한다.
플러시 발생하면?
- 변경 감지가 일어남.
- 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록한다.
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다. (등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 플러시 하는 방법
- em.flush - 직접 호출
- 트랜잭션 커밋 - 자동 플러시 호출
- JPQL 쿼리 실행 - 플러시 자동 호출
영속성 컨텍스트의 쓰기 지연 SQL 저장소에 있는 것들이 데이터베이스에 반영되는 과정이 플러시라고 보면 된다.
em.persist(memberA);
em.persist(memberB);
//여기까지 DB에 insert 쿼리 안날라감
query = em.createQuery("select m from Member m",Member.class);
//여기 중간에 JPQL을 실행하면 아무것도 조회되지 않음 = insert쿼리가 안날라갓으니까
하지만 중간에 flush를 하면 커밋이 날라간다.
플러시 옵션
플러시는 flush()를 날렸을 때 자동으로 쿼리가 날아가는게 기본값이다.
em.setFlushMode(FlushModeType.Auto); //기본값
영한님: 손 안대고 가급적 Auto로 쓰는거 추천..
플러시는..!
- 영속성 컨텍스트를 비우지 않는다.
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화한다.
- 트랜잭션이라는 작업 단위가 중요하다. → 커밋 직전(데이터베이스 반영 전) 에만 동기화 하면 됨.
준영속 상태
1차 캐시에 올라간 상태 = 영속 상태라고 볼 수 있다.
그럼 준영속 상태란 무엇일까?
영속성 상태에서 분리된 상태로, 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
준영속 상태로 만드는 법
1. em.detach(entity)
특정 엔티티만 준영속 상태로 전환하는법
Member member = em.find(Member.class,150L);
em.detach(member); //더이상 JPA가 관리하지 않는 상태가 된다.
em.detach(member); 를 하게 되면 member는 이제 준영속 상태로, 영속성 컨텍스트에서 관리하지 않는 형태가 된다.
직접 쓸 일은 없지만, 이해만 해주면 좋다.
2. em.clear()
영속성 컨텍스트를 완전히 초기화 (다시 시작)
3. em.close()
영속성 컨텍스트를 아예 종료
'BACKEND > Spring Boot' 카테고리의 다른 글
REST Template vs WebFlux (0) | 2024.11.05 |
---|---|
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 1일 차 JPA에 대해서 (4) | 2024.10.21 |
Spring Container는 어떻게 생성될까 (0) | 2024.09.22 |
Tomcat은 어떻게 동작하는가 (0) | 2024.09.22 |
테스트 스텁과 목 오브젝트의 차이점 (1) | 2024.09.19 |
안녕하세오 저는 똑똑해지고 싶은 버그 수집가에오
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!