JPA에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스의 매핑 (Object Relational Mapping)
- 설계와 관련된 부분
- 정적인 내용
- 영속성 컨텍스트
- 실제 JPA가 내부적으로 어떻게 동작하는가에 관련된 부분
1. 영속성 컨텍스트
1) 엔티티 매니저 팩토리와 엔티티 매니저
- 엔티티 매너지를 통해 영속성 컨텍스트에 접근한다.
- 영속성 컨텍스트는 논리적인 개념이다.
- J2SE 환경 : EntityManager와 PersistenceContext가 1:1로 관리된다.
- J2EE : 스프링과 같은 컨테이너 환경에서 EntityManager(N) : PersistenceContext(1)로 관리된다.
2) 영속성 컨텍스트
- JPA를 이해하는데 가장 중요한 용어이다.
- "엔티티를 영구 저장하는 환경"이라는 뜻이다.
- EntityManager.persist(entity); 로 코드에서 사용 가능하다.
2. 영속성의 생명 주기(Entity LifeCycle)
1) 비영속(new/transient)
- 순수한 객체 상태로 영속성 컨텍스트와 관련이 없는 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
2) 영속(managed)
- EntityManager를 통해 엔티티를 영속성 컨텍스트에 저장되어 영속성 컨텍스트가 관리되는 상태
EntityManager em = emf.createEntityManger();
em.getTransaction().begin();
em.persist(member); // 객체 저장
3) 준영속, 삭제
- 영속성 컨텍스트에 저장되었다가 분리(detached)된 상태
// 회원엔티티를 영속성 컨텍스트에서 분리, 준영속한 상태
em.detach(member);
// 객체를 삭제한 상태(삭제)
em.remove(member);
준영속 상태
1) detach() : 특정엔티티만 준영속 상태로 전환된다.
준영속 상태의 경우 더이상 엔티티를 관리하지 않고 1차 캐시, 쓰기지연 SQL 저장소에서 해당 엔티티를 관리하기 위한 모든 정보가 삭제되는 것이며, 실무에서 직접 사용하는 일이 거의 없다.
삭제
1) clear() : 영속성 컨텍스트를 완전히 초기화하여 해당 영속성 컨텍스트에 존재하는 모든 엔티티를 준영속 상태로 만든다. 모든 것이 초기화 되어 새로 만든 영속성 컨텍스트의 상태와 동일하다.
그래서 같은 Entity를 조회할 때 SELECT 쿼리가 발생하며 1차 캐시에 관계없는 쿼리를 확인하고 싶을 때 유용한 기능이다.
2) close() : 영속성 컨텍스트를 종료하고 관리하던 모든 엔티티가 준영속 상태가 된다.
3. 영속성 컨텍스트의 이점
- 1차 캐시
- 동일성 (identity) 보장
- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩 (Lazy Loading)
1) 엔티티 조회, 1차 캐시
- DB에 접근하기 전에 영속성 컨텍스트에 찾는 대상이 존재하면 DB에서 Select 하지 않고 캐시에 저장된 값을 반환한다.
// 엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("ellen");
member.setUsername("엘렌이");
// 엔티티를 영속, 1차 캐시에 저장됨
em.persist(member);
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
// 데이터 베이스에서 조회 -> 1차 캐시에 저장됨
Member findMember2 = em.find(Member.class, "member2");
2) 동일성 보장
- 1차 캐시로 반복 가능 읽기(REPEATABLE READ) 등급의 격리 수준을 애플리케이션 차원에서 제공할 수 있다.
- REPEATABLE READ는 동일 트랜잭션에서 조회한 결과가 동일함을 보장한다.
- DB의 4가지 특성(ACID) 중 격리성(Isolation)은 하나의 트랜잭션이 실행 중이면 다른 트랜잭션이 끼어들 수 없다는 특정을 가지며 끼어들 수 있는 정도를 4단계로 나누어 격리 수준을 구분한다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member2");
// 동일성 비교 => 결과 true
System.out.println(a == b);
3) 트랜잭션을 지원하는 쓰기 지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경할 때 트랜잭션을 시작해야 한다.
// 트랜잭션 시작
transaction.begin();
em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL문을 바로 DB에 전송하지 않음
// 커밋하는 순간에 DB에 INSERT SQL을 보낸다.
// 트랜잭션 커밋
transaction.commit();
4) 변경 감지
- JPA에서는 Update는 영속성 컨텍스트의 Entity가 변경되었는지를 기준을 처리한다.
- 따라서 commit 시점에 Entity가 변경되었는지 Checking하여 update가 자동으로 반영된다.
- Dirty Checking 하는 시점
- flush 하는 순간
- Entity에 대한 스냅샷을 저장하고 있다가 변경 내용에 대해 SQL을 만들고 반영한다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
// 영속 엔티티 조회
Member findMember = em.find(Member.class, "ellen");
// 영속 엔티티 데이터 수정
findMember.setUsername("hi");
findMember.setAge(10);
/**
em.update(member);
이런 코드가 존재하지 않아도 수정작업이 일어남
**/
transaction.commit();
5) 지연 로딩(Lazy Loading)
- 프록시 기술을 사용해 Entity에 대한 로딩(조회)를 지연 시킬 수 있다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
- 위와 같은 Member Entity가 존재할 때 연관관계인 Team에 대한 정보가 필요없을 때가 있다.
- 이 경우 Member에 대한 정보만 필요해서 Team까지 join해서 한번에 가져오면 비효율적이다.
- 지연 로딩의 반대되는 개념이 즉시로딩(Eager)이며 모든 정보를 한번에 다 가져오는 것이다.
- 지연 로딩은 처음에 Member에 대한 정보를는 임시로 만들어서 조회(프록시 조회)하며, Team에 대한 정보를 조회하는 시점에는 Team의 정보를 DB에서 Select 해온다.
실무에서는 가급적 지연 로딩만 사용해야 한다. 즉시 로딩의 경우 JPQL에서 N+1의 문제를 일으킨다.
@ManyToOne, @OneToOne은 default가 즉시 로딩이므로 Lazy로 바꿔줘야한다.
@OneToMany, @ManyToMany 는 default가 지연로딩이다.
4. 플러시
1) 플러시
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정이다.
2) 플러시 발생
- 변경 감지하며 수정된 엔티티의 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 한번에 데이터 베이스에 전송(등록, 수정, 삭제 쿼리)
3) 플러시 하는 방법
- em.flush() : 직접 호출
- 트랜잭션 커밋 : 플러시 자동 호출
- 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();
왜 memberA, B, C를 영속성 컨텍스트에 저장한 상태에서 바로 조회하면 조회가 될까?
- 조회가 안됨
- DB에 Query로 날라가야 반영이 되는데 INSERT Query 자체가 날라가지 않은 상태이다.
이런 상태에서 JPQL로 DB에서 가져오는 것은 SELECT를 요청한 것이므로 당연히 조회가 되지 않는다.
- JPQL은 SQL로 번역이 되어서 실행되는 것이다.
=> JPA의 기본 모드에서는 JPQL 쿼리 실행 시에 flush()를 자동으로 날리며, JPQL 쿼리를 실행하면 플러시를 자동으로 호출하여 위의 코드는 조회가 가능하다.
4) 특징
- 영속성 컨텍스트를 비우는 것이 아니다.
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것이다.
- 트랜잭션 단위로 처리된 다는 것이 중요하다.
- commit 이전에 동기화하면 된다.
5) 플러시 모드 옵션
- em.setFlushMode(FlushModeType.COMMIT);
- FlushModeType.AUTO : 기본 설정값이며 Transaction을 commit 하거나 Query 실행 시에 flush()를 먼저 수행한다.
- FlushModeType.COMMIT
- Transaction을 commit 할 때만 flush()를 먼저 수행한다. (Query 수행시 flush() 수행x)
- 장점 : persist 한 것 과 전혀 다른 테이블을 조회하는 경우에 이점이 있다.
'프로그래밍공부 > JPA' 카테고리의 다른 글
JPA의 엔티티 매핑 (0) | 2022.07.31 |
---|---|
JPA 시작 (0) | 2022.07.20 |
JPA란 (0) | 2022.07.17 |