관리 메뉴

JIE0025

[JPA] 3장 영속성 관리 본문

백엔드/스프링, 스프링부트, JPA, Spring Webflux

[JPA] 3장 영속성 관리

Kangjieun11 2022. 11. 5. 15:09
728x90

 

자바 ORM 표준 JPA 프로그래밍

김영한


 

 


 

영속성 관리

 

 

목차

  • 엔티티 매니저  & 엔티티 매니저 팩토리
  • 영속성 컨텍스트
  • 엔티티의 생명주기
  • 영속성 컨텍스트의 특징
    • CRUD)
  • 플러시
  • 준영속

 


 

✅ 엔티티 매니저 & 엔티티 매니저 팩토리

 

⏺ 엔티티 매니저  ( Entity Manager )

 

엔티티와 관련된 모든 일 (엔티티 CRUD) 을 처리하는 관리자

엔티티를 저장하는 가상의 데이터베이스라고 생각하면 됨

 

- 여러 스레드가 동시 접근하면 동시성 문제 발생  (스레드간 절대 공유 금지)

 

 

엔티티 매니저 팩토리  ( Entity Manager Factory )

엔티티 매니저를 만드는 공장

 

- 비용이 상당히 있음 🔼🔼

- 한개만 만들어 Application 전체에서 공유하도록 설계 됨

- 엔티티 매니저 팩토리가 엔티티 매니저를 만드는 비용 🔽🔽

- 여러 스레드가 동시 접근해도 안전

 

 

1) EntityManagerFactory가 EntityManager를 2개 생성

2) EntityManager1은 아직 DB 커넥션을 사용 x

  • 엔티티매니저는 연결이 꼭 필요할 때까지 커넥션 얻지 않음.
  • 보통 트랜잭션을 시작할 때 커넥션을 획득함.

 

 

💻 엔티티 매니저 팩토리에서 엔티티 매니저 생성

EntityManager em = emf.createEntityManager();

 


 

✅ 영속성 컨텍스트

엔티티를 영구 저장하는 환경

엔티티매니저는 영속성 컨텍스트 안에 엔티티를 보관하고 관리

엔티티매니저를 생성할 때 영속성 컨텍스트가 1개 만들어진다.

* 여러개의 엔티티 매니저가 같은 영속성 컨텍스트에 접근할 수도 있음

 

 

💻persist() 메소드

엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장

em.persist(member);

 

 

 


 

✅ 엔티티의 생명주기

크게 4가지 생명주기 (비영속/ 영속/준영속 /삭제) 존재

엔티티매니저가 생명주기 상태들을 전환하는 메서드를 제공

 

 

 

이름 의미 설명
New / transient 비영속 - Java 영역에만 존재
- DB와 연동된적 없는 상태
- 순수한 Java 객체, 엔티티 매니저가 관리 x
Managed 영속 - DB에 저장되고 메모리상에서도 같은 상태로 존재함.
- PK값을 통해 필요한 엔티티 객체를 꺼내 사용할 수 있음
Removed 삭제 - DB상에서 삭제된 상태
- 삭제된 객체는 영속 콘텍스트에 존재하지 않음.
Detached 준영속 - 영속 컨텍스트에서 엔티티 객체를 꺼내서 사용하는 상태
- 아직 DB와 동기화가 이루어지지 않은 상태

 

💻 엔티티 생명주기 코드와 함께 확인

//비영속(new)
Member member = new Member();

//영속(managed)
em.persist(member); // 객체 저장

//준영속(detached)
em.detach(member); // 특정 엔티티를 분리
em.close(); // 영속성 컨텍스트 닫기
em.clear(); // 영속성 컨텍스트 초기화

//삭제(removed)
em.remove(member);

 

비영속 (new),    영속(persist) 상태의 그림

 


 

✅ 영속성 컨텍스트의 특징

 

  • 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본 키와 매핑한 값) 으로 구분한다.
    • 영속상태는 반드시 식별자 값이 있어야 한다.     (없으면 예외발생💥)
  • 영속성 컨텍스트에 저장된 엔티티언제 DB에 저장될까?
    • JPA는 트랜잭션을 커밋하는 순간, 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영 함. 

 

 

영속성 컨텍스트를 통해 얻는 이점

영속성 컨텍스트를 중간에 추가해서 메커니즘이 복잡해졌다   대체  왜 사용할까? (WHY)

 

영속성 컨텍스트를 사용함으로써 5가지의 이점이 있다. 

 

1) 1차캐시

2) 동일성 보장

3) 트랜잭션을 지원하는 쓰기 지원

4) 변경 감지 (dirty cheking)

5) 지연 로딩

 

 

* CRUD를 통해 자세히 설명

 

 

⏺ 엔티티 조회 (Read)

 

1)  1차 캐시

영속성 컨텍스트 내부에 있는 캐시 

영속 상태 (Managed) 엔티티들은 모두 이곳에 저장

 

(( Map ))   key = @Id  :   Value =  엔티티 인스턴스 

 

💻 예제 코드 

Member member = new Member();  // Transient or New
member.setId("member1");
member.setUsername("회원1");

em.persist(member);  //Managed

 

1 : Transient or New 상태
만약 이 코드만 실행하고 persist 없이 트랜잭션 종료시 db상엔 아무런 변화도 일어나지 않는다.

 

2-3 : member 인스턴스의 Id 와 Usernaem을 설정했다.

 

4 : 엔티티매니저를 통해 영속성 컨텍스트에 member를 넣었다. 즉 영속상태(managed)가 되었다.

managed 상태의 엔티티들은 엔티티 매니저에 의해 `변경감지`를 인식,

flush(Persistence Context 내용을 DB에 반영) 호출시 변경 필드에 의해 자동으로 update/insert 된다. 

 

 

코드 실행 후 1차 캐시에 엔티티가 저장된 모습이다.

 

💻 find() : 1차 캐시에서 엔티티 조회

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

 

* em.find() 호출했는데, 1차 캐시에 엔티티가 없을 경우엔 DB를 조회해 엔티티를 생성

* 이후 1차 캐시에 저장하고, 영속 상태의 엔티티를 반환

 

Member member2 = em.find(Member.class, "member2");

 

 


 

2) 동일성 보장

DB에서 조회한 Data를 기반으로 새로운 Entity를 생성하는게 아닌,

1차 캐시에서 삽입/조회를 해서 동일성이 보장된다.  

 

findUser1과 findUser2는 같은 객체를 바라본다

User findUser1 = em.find(User.class, "user1");
User findUser2 = em.find(User.class, "user1");
System.out.println(findUser1 == findUser2); // true

 

 

 

* 동일성 (identity) : 실제 인스턴스가 같음 ( 자바의 == 참조값 비교 연산 시 true)

* 동등성(equality) : 실제 인스턴스는 다를 수 있다. 인스턴스가 가진 값이 같음 (자바 equals()와 비슷한 느낌)

 

 


 

⏺ 엔티티 등록 (Create)

 

3) 트랜잭션을 지원하는 쓰기 지원

쓰기 지연은 em.persist(memberA)라는 메소드를 호출했을 때,
바로 insert 쿼리문을 날리는게 아니라, 영속성 컨텍스트에 집어넣고
트랜잭션을 커밋하기 직전까지  <쓰기 지연 SQL 저장소>에 insert 쿼리문을 쌓아둠

 

 

memberA 영속화 

 1차캐시에 엔티티 저장   AND  쓰기 지연 SQL 저장소에  [ 등록쿼리 ] 보관

 

memberB 영속화

 1차캐시에 엔티티 저장  AND  쓰기 지연 SQL 저장소에  [ 등록쿼리 ] 보관

 

트랜잭션 커밋

영속성 컨텍스트 flush *변경내용 DB에 동기화  (CRUD)  → 실제 DB에 트랜잭션 commit 으로 반영  

 

 

 

 


 

 

 

⏺ 엔티티 수정 (Update)

 

SQL 수정쿼리의 문제점 

SQL수정 쿼리의 문제 

SQL을 사용하면, 수정쿼리를 직접 작성해야한다. 

프로젝트가 점점 커지면서 요구사항이 많아지고, 수정쿼리도 점점 증가한다. 

 

이 방식은 결국 수정쿼리도 많아지며, 비지니스 로직을 분석하기 위해 SQL을 확인해야한다. (로직이 SQL에 의존적)

 

 

JPA 영속성 컨텍스트를 통한 극복

JPA로 엔티티를 수정 할 때 단순히 엔티티를 조회해서 데이터만 변경해주면 된다.  

엔티티의 데이터만 변경해도 DB에 반영이 된다. 

 

 

 

4) 변경 감지 (dirty cheking)

엔티티의 변경사항을 DB에 자동으로 반영하는 기능

 

 

 

<< 이미지 출처  >>

 

Member엔티티에 name을 설정하고, DB를 확인하면 Null이 확인된다. 

em.update같은 메서드 없이 JPA가 알아서 반영한다. 

 

 

 

 

 

* JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 저장함  (== 스냅샷)

 

1) 트랜잭션 커밋하면 엔티티매니저 내부에서 플러시(flush) 호출

2) 엔티티와 스냅샷을 비교해 변경된 엔티티를 찾음

3) 변경된 엔티티 존재할 경우 수정쿼리 생성, 쓰기 지연 SQL 저장소로 보냄

4) 쓰지 지연 저장소의 SQL을 DB로 보냄

5) DB 트랜잭션을 커밋

 

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

 

 

*회원의 이름과 나이만 변경됐다고 가정했을 때 이 두개만 변경할것 같지만,

JPA 기본 전략으로 엔티티의 모든 필드를 업데이트 함. 

 

단점

  • 데이터 전송량이 증가

 

장점

  • 모든 필드를 사용시 수정쿼리가 항상 같음 - application 로딩시점에 쿼리 미리 생성 후 재사용 가능
  • DB에 동일한 쿼리를 보내면, DB는 이전에 한번 파싱된 쿼리를 재사용 가능

 

 

5) 지연 로딩 (Lazy Loading)

연관관계 매핑되어 있는 엔티티 조회시, 프록시를 반환해서 쿼리를 진짜 필요할 때 날리는 기능이다.

 

 


 

⏺ 엔티티 삭제 (Delete)

 

삭제할 엔티티를 조회 후, remove()에 넘겨주면 엔티티가 삭제된다.

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

 

1) 즉시 삭제 하지 않고, 엔티티 등록과 비슷하게 삭제 쿼리를 SQL 저장소에 등록한다. 

2) 트랜잭션 커밋해서 플러시 호출하면, 그때 DB에 삭제 쿼리를 전달한다. 

 

*em.remove()를 호출하는 순간 엔티티는 영속성 컨테스트에서 제거된다.

 

 

 


 

 

✅ 플러시

 

영속성 컨텍스트의 변경 내용을 DB에 반영함 (동기화)

 

플러시 흐름

1) 변경감지 동작해 스냅샷과 비교, 수정된 엔티티 발견

2) 수정된 엔티티를 수정쿼리를 만들고 SQL 저장소에 등록

3) 쓰기 지연 SQL저장소의 쿼리를 DB에 전송

 

 

플러시 하는 방법 3가지

1) em.flush()                   - - 직접 호출

flush() 메서드를 직접 호출해서 영속성 컨텍스트를 강제로 플러시

테스트, 다른 프레임워크와 JPA를 함께 사용할 때 사용

 

2) 트랜잭션 커밋시          - - 자동 호출

SQL이 전달되지 않은 채 DB 트랜잭션 커밋만 되면 반영이 되지 않기 때문에 , 트랜잭션 커밋시 이전에 플러시가 꼭 호출 되어야한다. 

이러한 이유로 JPA는 트랜잭션을 커밋할 때 자동으로 플러시를 호출한다. 

 

3) JPQL 쿼리 실행           - - 자동 호출

JPQL을 실행하면 SQL로 변환되어 DB에서 엔티티를 조회한다. 이 상황에서 DB에 반영이 되어있지 않으면, 쿼리 결과로 조회될 수 없다.  그래서 쿼리를 실행하기 전에 영속성 컨텍스트를 플러시 해서 변경내용을 반영해야한다.  (결국 예방하기 위해 자동 호출)

 

 

⏺ 플러시 옵션

javax.persistence.FlushModeType    : 엔티티 매니저에 플러시 모드를 직접 지정 

- FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 (default)

- FlushModeType.COMMIT : 커밋할 때만 플러시

 

* 플러시 모드 별도 설정 x 일 경우, AUTO로 동작되고  대부분은 기본설정을 그대로 사용함

 

 

 


 

 

✅ 준영속

 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된(detached) 상태

 

* 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다. 

 

⏺ 준영속 상태로 만들기  (영속 → 준영속)  

 

◼ em.detach(entity)

특정 엔티티만 준영속 상태로 전환

public void detach(Object entity);

1) detach(memberA) 호출 (영속성 컨텍스트에게 해당 엔티티를 관리하지 말라고 말하는 것)

2-2) 1차 캐시, 쓰기지연 SQL저장소의 해당 엔티티를 관리하기 위한 모든 정보가 제거 됨. (쓰기 지연 SQL 저장소에 있던 SQL도 제거됨)

 

 

◼ em.clear() 

영속성 컨텍스트를 완전히 초기화

모든 엔티티가 준영속 상태로 만들어진다. 

 

 

 

◼ em.close()

영속성 컨텍스트를 종료해 영속상태의 엔티티가 모두 준영속 상태가 된다. 

* 주로 영속성 컨텍스트가 종료되면서 준영속 상태가 되고, 개발자가 직접 준영속으로 만드는 일은 드물다.

 

 

 

 

 

⏺ 준영속 상태 특징

- 거의 비영속 상태와 가까움 (영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않음)

- 식별자 값이 있다 (비영속상태는 없을수도 있다는것이 차이점)

- 지연 로딩을 할 수 없다.

 

 

⏺ 병합 merge()  (준영속 → 영속)  

 

 

// merge() 메서드 정의
public <T> T merge(T entity);

// merge() 사용 예
Member mergeMember = em.merge(member);

 

 

* 비영속 엔티티도 영속 상태로 만들 수 있다. 

Member member = new Member();
Member newMember = em.merge(member);  // 비영속 병합
tx.commit();

 

 


 

references

자바 ORM 표준 JPA 프로그래밍

 

자바 ORM 표준 JPA 프로그래밍 - YES24

자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고

www.yes24.com

 

https://velog.io/@minjoon1324/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8

https://girawhale.tistory.com/122#%EC%A4%80%EC%98%81%EC%86%8D-%EC%83%81%ED%83%9C

https://steady-coding.tistory.com/519