우선 내가 지금까지 JPA 기본편과 활용편을 수강하면서 중요하다고 생각한 개념에는 4가지가 있다.
1. 영속성 컨텍스트
2. 연관관계 매핑
3. 프록시 개념
4. 즉시 로딩과 지연 로딩
이 4가지는 꼭 개념 정리를 확실히 해서 기록해보고자 한다.
영속성 컨텍스트
"엔티티를 영구 저장하는 환경"이라는 뜻
엔티티 매니저? 영속성 컨텍스트?
고객이 요청을 할 때마다 EntityManagerFactory가 엔티티매니저를 생성하고 커넥션을 사용해서 DB를 사용하게 된다.
• 영속성 컨텍스트는 논리적인 개념이다.
• 엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.
• 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
엔티티의 생명주기
비영속 (new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
영속 (managed)
영속성 컨텍스트에 관리되는 상태
(영속 상태가 되었다고 해서 DB에 저장된 게 아니다)
//객체를 저장한 상태(영속)
em.persist(member);
준영속 (detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
삭제 (removed)
객체를 삭제한 상태
//객체를 삭제한 상태(삭제)
em.remove(member);
영속성 컨텍스트의 이점
• 1차 캐시
ex) 고객이 동시에 10명이 접근하면 다 별도의 1차 캐시를 가진다. (성능보다는 매커니즘의 이점이 존재)
• 동일성(identity) 보장
1차 캐시에서 가져온 걸 사용한다.
• 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
wirte를 모았다가 한 번에 write 하는 것
• 변경 감지 (Dirty Checking)
JPA가 자동으로 변경을 감지하는 것
엔티티 조회 흐름
persist() 하는 순간 바로 쿼리가 날아가는 게 아니라 1차 캐시에 저장해 놓는다.
→ 영속상태가 된다.
사실상 1차 캐시를 영속성 컨텍스트라고 이해해도 된다.
1차 캐시에서 조회
persist 후 em.find()로 조회하면 DB를 뒤지는 게 아니라 1차 캐시에서 조회한다.
데이터베이스에서 조회
만약 member2를 조회한다면 1차 캐시에서 찾는다.
member2는 이전에 저장됐던 값이므로 1차 캐시에 없기 때문에 DB에서 조회한다.
em.persist()의 과정
commit 하면 모아놨던 insert쿼리를 DB에 전송한다.
트랜잭션 관리와 플러시
트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
persist()를 여러 번 해도 그때그때 insert 쿼리가 나가는 게 아니라 1차 캐시에 모아놨다가 commit()하는 순간 flush가 되고 쿼리가 날아간다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
변경 감지 (Dirty Checking)
내가 항상 해왔던 걸 생각하면 회원의 정보를 수정할 때는
member.setUserName("hi");
member.setAge(10);
memberService.updateMemberInfo(~~);
이렇게 필드를 다시 set 해주고 update쿼리를 날리는 메소드를 실행해줘야 했다.
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
하지만 JPA는 저렇게 set만 해주는데 변경을 감지해서 DB에 update문을 날려준다. 어메이징..
그 원리는 1차 캐시 안에 스냅샷에 최초 시점의 상태를 저장해 놓고 커밋하는 순간 엔티티와 이 스냅샷을 비교한다.
그리고 바뀐 엔티티가 있다면 쓰기 지연 SQL 저장소에 저장해 놨다가 한꺼번에 쿼리를 날린다.
플러시
• 영속성 컨텍스트의 변경 내용(쓰기 지연 SQL 저장소)의 쿼리를 DB에 날리는 것
• commit 하기 전에 강제로 날리는 것
• 변경 감지
영속성 컨텍스트를 플러시 하는 방법
1. em.flush() - 직접 호출(웬만하면 자동 호출을 권장한다.)
//영속
Member member = new Member(200L, "member200");
em.persist(member);
em.flush();
System.out.println("=================");
tx.commit();
2. 트랜잭션 커밋 - 플러시 자동 호출
3. JPQL 쿼리 실행 - 플러시 자동 호출
플러시 모드 옵션
굳이 건들지 말자
em.setFlushMode(FlushModeType.AUTO) : 커밋이나 쿼리를 실행할 때 플러시(기본값)
em.setFlushMode(FlushModeType.COMMIT) : 커밋할 때만 플러시
준영속 상태
• 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)된 상태
• 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.
준영속 상태로 만드는 방법
em.detach(entity)
특정 엔티티만 준영속 상태로 전환
//영속
Member member1 = em.find(Member.class, 200L);
member1.setName("AAA");
//준영속 상태로 만듦
em.detach(member1);
System.out.println("=================");
//commit해도 아무 일도 일어나지 않음.
tx.commit();
em.clear()
영속성 컨텍스트를 완전히 초기화
//영속
Member member1 = em.find(Member.class, 200L);
member1.setName("AAA");
em.clear();
Member member2 = em.find(Member.class, 200L);
System.out.println("=================");
tx.commit();
영속성 컨텍스트가 초기화 됐기 때문에 select문이 2번 나감
em.close()
영속성 컨텍스트를 종료
[참고] 인프런 - 김영한 님의 자바 ORM 표준 JPA 프로그래밍-기본편 강좌를 보고 공부한 내용을 바탕으로 작성했습니다.
'💻 Dev > JPA' 카테고리의 다른 글
변경감지와 병합 (0) | 2024.01.06 |
---|---|
단방향, 양방향 연관관계 매핑 (0) | 2023.06.23 |
연관관계가 필요한 이유 (0) | 2023.06.22 |
엔티티 매핑 (0) | 2023.06.17 |