자바 ORM 표준 JPA 프로그래밍 - 기본편 챕터 1-3 정리

정종일·2023년 5월 22일
0

Spring

목록 보기
8/18

1. Entity Manager Factory & Entity Manager


Entity? EMF? EM?

Entity Manager Factory

  • EntityManager를 생성하는 객체
  • 하나의 EntityManagerFactory는 어플리케이션 전체에서 공유됨 (싱글톤)
  • EntityManagerFactory를 생성할 때 Connection pool도 함께 생성
  • 다중스레드 공유 가능

** connection pool ?
사용자의 요청에 따라 Connection 을 생성하다 보면 많은 수의 연결이 발생했을 때 서버에 과부하가 걸리게 된다 . 이러한 상황을 방지하기 위해 미리 일정수의 Connection 을 만들어 pool 에 담아 뒀다가 사용자의 요청이 발생하면 연결을 해주고 연결 종료 시 pool 에 다시 반 환하여 보관하는 것이다 .

Entity Manager

  • Entity를 관리하는 객체
  • DB연결이 꼭 필요한 시점까지 커넥션을 얻지 않음
  • 모든 데이터 변경은 트랜잭션 안에서 이루어져야 하며 트랜잭션을 시작할 때 커넥션 획득
  • 쓰레드간에 공유를 하게되면 동시성의 문제가 발생가능함!!!

Entity

  • Entity Manager가 관리하는 객체
  • 테이블과 커넥션 관리
// Entity Manager Factory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("UserPlatformDev");

// Entity Manager
EntityManager em = emf.createEntityManager();

동작 과정

출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

  1. 설정 파일을 통해서 JPA 설정
  2. Entity Manager Factory를 생성
  3. Entity Manager를 생성하여 Entity를 영속성 컨텍스트로 관리

2. 영속성 컨텍스트


영속성 컨텍스트

  • '엔티티를 영구 저장하는 환경' 이라는 뜻
em.persist(member);
  • 위 코드는 단순히 회원 엔티티를 저장한다 표현하는 것이 아닌
    '엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장' 한다고 표현

3. 엔티티 생명주기

엔티티에는 4가지 상태가 존재

  • 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
    • 엔티티 객체를 생성, 순수한 객체상태이며 아직 저장하지 않음

      Member member = new Member();
      member.setId("UserPlatformDev Member");
      member.setUsername("jongil");
  • 영속(managed) : 영속성 컨텍스트에 저장된 상태
    • 영속성 컨텍스트가 관리하는 엔티티. JPQL을 사용해서 조회한 엔티티도 영속상태

      em.persist(member);
  • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
    // 분리 명령어
    em.detach();
    em.close();
  • 삭제(removed) : 삭제된 상태
    • 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제

      em.remove(member);

출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

4. 영속성 컨텍스트의 특징


  1. 영속성 컨텍스트와 식별자 값
    • 영속상태는 반드시 식별자 값이 존재해야함
      @Id
      private Long sn;
  2. 영속성 컨텍스트와 데이터베이스 저장
    • JPA는 트랜잭션을 커밋하는 순간 데이터베이스에 반영
    • flush라 칭함
  3. 영속성 컨텍스트가 엔티티를 관리하면 아래와 같은 장점이 존재
    • Entity의 생명주기를 관리
    • 1차캐시라는 메모리공간으로 Entity를 관리하여 중복쿼리 최소화, Entity 동일성 보장
    • 쓰기지연, 변경감지, 지연로딩으로 성능 최적화에 유리

엔티티 조회

영속성 컨텍스트는 내부에 캐시를 가지고 있다. 이를 1차 캐시라 한다.

쉽게 말하면 내부에 Map이 하나 있고 @Id가 key값이라 생각하면 된다.

// 비영속 상태
Member member = new Member();
member.setId("UserPlatformDev Member");
member.setUsername("jongil");

// 영속 (DB에 저장되지는 않음)
em.persist(member);
Member member = em.find(Member.class, "jongil");

em.find를 호출하면 먼저 1차캐시에서 엔티티를 찾고 없으면 데이터베이스에서 조회한다.

Member member = em.find(Member.class, "jjj");

1차캐시에 없는 데이터일 경우 DB를 조회해서 엔티티를 생성하고 1차캐시에 저장 후 영속상태의 엔티티를 반환한다.

Member a = em.find(Memeber.class, "jongil");
Member b = em.find(Memeber.class, "jongil");

// a == b 는 참 일까?

당연히 참이다. 1차캐시에 있는 같은 엔티티 인스턴스를 반환하므로 동일성을 보장한다.

엔티티 등록

엔티티 매니저를 사용하여 엔티티를 영속성 컨텍스트에 등록해보자

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

// 트랜잭션 시작
transaction.begin();

// INSERT SQL을 DB에 보내지않음
em.persist(memberA);
em.persist(memberB);

// 커밋하는 순간 보냄
transaction.commit();

엔티티 매니저는 트랜잭션 커밋 직전까지 DB에 엔티티를 저장하는 것이 아닌 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 커밋할 때 한번에 DB에 보내는데 이것이 바로 쓰기지연 !

이를 잘 활용하면 모아둔 등록쿼리를 DB에 한번에 전달해서 성능을 최적화 할 수 있다

엔티티 수정

SQL 수정쿼리의 문제점

  • 요구사항에 따라 수정쿼리가 점점 추가되어 수정쿼리가 많아짐
  • 비지니스 로직을 분석하기 위해 SQL을 계속 확인해야함
  • 직˙간접적으로 비지니스 로직이 SQL에 의존하게 됨

변경 감지

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

Member member = em.find(Member.class, "jongil");

member.setUsername("jjj");
member.setAge(29);

// em.update(member); // 왜 없을까 ?

transaction.commit();

JPA는 변경사항을 명령어 없이 자동으로 감지하여 반영을하는데 이를 변경감지라고 한다.

변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용 !

변경감지 동작 과정

  1. 트랜잭션 커밋 → 엔티티매니저 내부에서 flush 호출
  2. 엔티티와 스냅샷을 비교하여 변경된 엔티티를 찾음
  3. 변경된 엔티티가 존재하면 수정쿼리를 생성하여 쓰기지연 SQL 저장소에 보냄
  4. 쓰기지연 저장소의 SQL을 DB에 보냄
  5. DB 트랜잭션 커밋

** 스냅샷 : 최초 상태를 복사해 저장해두는 것

DynamicInsert, DynamicUpdate

Update를 할 때 한개의 컬럼을 업데이트하면 한가지만 Set 하는 쿼리가 날아갈까?

아니다. JPA의 기본 전략은 모든 필드를 업데이트 한다.

전송량이 증가할텐데 왜?

  1. 모든 필드를 수정하면 수정쿼리가 항상 동일, 재사용성 증가
  2. DB에 동일한 쿼리를 보내면 DB는 이전에 한번 파싱된 쿼리를 재사용 할 수 있음

컬럼이 대략 30개 이상이 되면 기본 방법인 정적 수정쿼리보다 동적 수정쿼리가 필요하다. 이 때 사용하는 것이 바로 @DynamicUpdate 이다!

필요한 부분 (부분 null 인 entity 등)만 insert 가능한 @DynamicInsert 도 있다!

엔티티 삭제

Member member = em.find(Member.class, "jongil");
em.remove(member);

em.remove();

엔티티 삭제 또한 바로 삭제되는 것이 아닌 삭제쿼리를 쓰기 지연 SQL에 등록 후 트랜잭션 커밋하여 플러시를 호출하면 삭제 ! 삭제된 엔티티는 재사용하지 않는것이 좋다

5. 플러시


플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영한다 !

동작과정

  1. 변경감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교 후 수정된 엔티티를 찾음
  2. 수정된 엔티티는 수정쿼리를 만들어 쓰기지연 SQL 저장소에 등록
  3. 쓰기지연 SQL 저장소의 쿼리를 DB에 전송

영속성 컨텍스트 플러시 방법

  1. em.flush() 를 호출

  2. 트랜잭션 커밋 시 플러시 자동 호출

  3. JPQL 쿼리 실행 시 플러시가 자동 호출

    em.persist(memberA);
    em.persist(memberB);
    em.persist(memberC);
    // DB 미반영 상태
    
    query = em.createQuery("select m from Member m", Member.class);
    List<Member> members = query.getResultList();
    • JPQL 실행전에 영속상태의 엔티티들이 있다면 먼저 반영해야하기 때문에 find() 메소드를 제외하고 플러시 자동호출!

플러시 모드 옵션

  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 (default)
  • FlushModeType.COMMIT : 커밋할 때만 플러시
    • 성능 최적화를 위해 사용할 수 있음 (10장 6절에서 알려준대용.. 그때까지 화이팅)

⚠️ flush는 영속성컨텍스트에 보관된 Entity를 지우는 것이 아닌 변경 내용을 DB에 동기화하는것 !

6. 준영속


준영속?

영속상태였다가 더는 영속성 컨텍스트가 관리하지 않는 상태

준영속 상태로 만드는 방법

  1. em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
  2. em.clear() : 영속성 컨텍스트를 완전히 초기화
  3. em.close() : 영속성 컨텍스트를 종료

detatch ();

public void detach(Object entity);

// 비영속 상태 
Member member = new Member(); 
member.setId("jongil"); 
member.setUsername("종일"); 
 
// 영속 상태 
em.persist(member); 

// 영속성 컨텍스트에서 분리(준영속 상태) 
em.detach(member); 
 
transaction.commit(); // 커밋

위와같이 중간에 detach를 하게되면 영속성 컨텍스트로부터 분리(detach)된 상태이기 때문에 commit을 하더라도 반영이 되지 않는다.

clear ();

detach는 부분 분리이지만 clear는 영속성 컨텍스트를 초기화하는것 !

휴지통 비우기 : clear / 휴지통에 파일 삭제 : detach 라 생각하면 쉬울것같다 !

close ();

영속 상태의 엔티티가 모두 준 영속 상태가 된다.

영속성 컨텍스트를 종료한다 생각하면 된다.

개발자가 직접 준영속 상태로 만드는 일은 드물다 ! 잘 알아두기만 하자

준영속 상태의 특징

  1. 비영속에 가까움
    • 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않음!
  2. 식별자 값을 가지고 있음
    • 이미 한번 영속상태였으므로 식별자 값을 가지고있음!
  3. 지연로딩이 불가함
    • 지연로딩은 영속성 컨텍스트를 통해 데이터를 불러오는 방법이다. 준영속상태는 영속성 컨텍스트가 관리하지 않으므로 문제 발생 ! (문제는 8장에서 설명한다)

merge();

준영속 상태의 엔티티를 다시 영속상태로 변경하는 방법이다. 새로운 영속 상태의 엔티티를 반환.

profile
제어할 수 없는 것에 의지하지 말자

0개의 댓글