[자바 ORM 표준 JPA 프로그래밍] - 3. 영속성 관리

쓰옹·2023년 5월 10일
0

JPA

목록 보기
3/4

김영한, ⌜자바 ORM 표준 JPA 프로그래밍⌟ 과
인프런 강의 <자바 ORM 표준 JPA 프로그래밍 - 기본편> 으로
공부한 JPA를 정리한 내용이다.

EntityManagerFactory & EntityManager

앞선 포스팅에서도 언급했듯 엔티티매니저를 사용해 엔티티와 관련된 일을 처리하는데 엔티티매니저를 사용하려면 엔티티매니저팩토리에서 필요할 때마다 생성해야함.

EntityManagerFactory

  • 상당한 비용 소모
    • 어플리케이션 전체에서 공유
  • 여러 스레드 동시 접근 가능 == 스레드 간 공유 가능

EntityManager

  • 엔티티매니저팩토리에서 생성
    • 비용 거의 안듦
  • 여러 스레드 동시 접근 시 동시성 문제 발생 == 스레드 간 공유 절대 놉!

JPA가 실제 매핑한 엔티티를 사용할 때 엔티티매니저를 통해 영속성 컨텍스트에 접근이 가능하고 엔티티를 영속성 컨텍스트에 저장함.

영속성 컨텍스트

Persistence Context

  • 엔티티를 영구히 저장하는 환경. 엔티티를 보관하고 관리함.
  • 엔티티매니저 : 영속성 컨텍스트 = 1 : 1
    • 스프링프레임워크에서는 N : 1 가능
  • em.persist() -> 영속성컨텍스트에 저장함(영속화)

엔티티 생명주기

비영속(new/transient), 영속(managed), 준영속(detached), 삭제(removed)

비영속

only 객체를 생성만 한 상태
영속성컨텍스트나 데이터베이스와는 전혀 무관련

Member member = new member();
member.setId(100L);
member.setName("A");

영속

영속성컨텍스트가 엔티티를 관리하는 상태

public class JpaMain {
	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
		EntityManager em = emf.createEntityManager();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		try {

			// 비영속. 객체를 생성만 한 상태
			Member member = new Member();
			member.setId(100L);
			member.setName("helloJPA");

			// 영속. 객체를 저장한 상태
			System.out.println("========= BEFORE =========");
			em.persist(member); // 저장
			System.out.println("========= AFTER =========");

			tx.commit(); // 트랜잭션 커밋
		} catch (Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}
		emf.close();
	}
}

em.persist(member) -> DB 저장은 아님. 커밋이 되야 쿼리가 날라감.

준영속

영속 상태 엔티티를 영속성 컨텍스트가 관리하지 않는 상태.
영속성 컨텍스트에서 분리.
-> 영속성 컨텍스트가 제공하는 기능 사용 불가

  • 준영속 상태로 만드는 방법
    1. em.detach(entity) : 특정 엔티티만 준영속 상태로 전환

    try {
    	Member member = em.find(Member.class, 100L);
    	member.setName("AAAAAA"); // "helloJPA" -> "AAAAAA"
        
    	em.detach(member);
        
    	tx.commit();
    }

    위에서 생성된 member를 조회하고 수정하면 업데이트 쿼리가 날라가야하지만 em.detach(member); 로 해당 엔티티를 준영속상태로 전환했기 때문에 업데이트 쿼리가 생성되지 않음. 해당 엔티티를 관리하기 위한 모든 정보가 영속성컨텍스트에서 제거됨.
    2. em.clear() : 영속성컨텍스트 초기화

    try {
    	Member member = em.find(Member.class, 100L);
    
    	em.clear();
        
        Member member2 = em.find(Member.class, 100L);
        
    	tx.commit();
    }

    위 경우엔 select 쿼리가 2번 발생함
    3. em.close() : 영속성 컨텍스트 종료

주로 영속성컨텍스트가 종료되면서 준영속상태가 됨.
개발자가 직접 준영속상태로 만드는 경우는 거의 없음

준영속 상태 특징

  • 거의 비영속 상태
  • 식별자 값을 갖고 있음
    - 이미 한 번 영속 상태였으므로 반드시 갖고 있음
  • 지연로딩 불가

삭제

em.remove(member)
엔티티를 영속성컨텍스트와 데이터베이스에서 삭제

영속성컨텍스트 특징

  • 식별자 값(@Id, PK)
    영속상태는 반드시 식별자 값이 있어야 함 (없으면 예외 발생)
  • 데이터베이스 저장
    플러시(flush) : 트랜잭션을 커밋하는 순간 데이터베이스에 반영
  • 장점
    • 1차캐시
    • 동일성 보장
    • 트랜잭션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연로딩

1차캐시

Member member = new Member();
member.setId(101L);
member.setName("helloJPA");

// 영속. 객체를 저장한 상태
System.out.println("========= BEFORE =========");
em.persist(member); // 1차캐시에 저장
System.out.println("========= AFTER =========");

Member findMember = em.find(Member.class, 101L); 
// 1차캐시에 있는 애를 조회. 조회쿼리 안날라감

System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());

tx.commit(); // 트랜잭션 커밋

기능 호출이 오면 앤티티매니저가 생성되고 엔티티매니저는 트랜잭션 종료 시 지우기 때문에 한 트랜잭션 안에서만 1차캐시가 작동함
그래서 찰나의 순간에만 이득 있기 떄문에 성능적으로는 크게 이점은 없음.
1차캐시의 컨셉이 주는 이점이 있는 것.
비즈니스 로직이 복잡해서 한 트랜잭션이 겁나 복잡하면 성능적으로 이점이 있을수도..

* JPA는 1차캐시를 통해 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 어플리케이션 차원에서 제공함

엔티티 동일성 보장

동일성(identity)
실제 인스턴스가 같음. 참조값, 주소값이 같음. ==비교 값이 같음

Member mem1 = em.find(Member.class, 100L);
Member mem2 = em.find(Member.class, 100L);

System.out.println("result = " + (mem1 == mem2)) // result = true

반복해서 호출하더라도 1차캐시에 있는 엔티티 인스턴스를 반환하기 때문

트랜잭션을 지원하는 쓰기 지연

엔티티매니저는 쓰기지연SQL저장소가 있지
트랜잭션을 커밋하기 직전까지 데이터베이스에 쿼리를 날리지 않고 쓰기지연저장소에 쿼리를 모아둠. 커밋하면 모아둔 쿼리를 한 번에 데이터베이스에 보냄

Member member1 = new Member(140L, "A");
Member member2 = new Member(130L, "B");

em.persist(member1);
em.persist(member2);
System.out.println("=================="); // 이거 이후 쿼리 두개 날라감

tx.commit(); // 트랜잭션 커밋


트랜잭션 범위 내에서 내부 로직은 함께 저장되고 롤백을 할 경우 함께 저장되지 않기 때문에 트랜잭션 켜밋 직전에만 데이터베이스에 쿼리를 전달하면 됨.
성능 최적화 가능
이걸로 버퍼기능도 사용 가능 (하이버네이트 제공 @BatchSize 설정) 등등

변경감지 (Dirty Checking)

엔티티 변경사항을 감지해서 데이터베이스에 자동으로 반영
1차캐시에 있는 스냅샷(최초 상태를 복사해서 저장함)과 비교하여 변경 감지

Member member = em.find(Member.class, 150L);
member.setName("ZZZ"); // 더티체킹으로 업데이트쿼리가 날라가지
// em.persist(member); // 자바 컬렉션에서 값 변경하는 것처럼 해당 코드는 필요없음.

tx.commit(); // 트랜잭션 커밋


트랜잭션 커밋 -> 엔티티매니저 내부에서 플러시 호출 -> 엔티티와 스냅샷 비교 후 변경된 엔티티 검색 -> 변경된 엔티티: 수정쿼리 생성 후 쓰기지연저장소로 전송 -> 쓰기지연저장소의 SQL 데이터베이스로 전송 -> 트랜잭션 커밋

profile
기록하자기록해!

0개의 댓글