본문 바로가기
개발공부 개발새발/DB

JPA) CRUD, 영속성 컨텍스트, 플러시, 준영속상태

by 휴일이 2022. 11. 24.


JPA와 CRUD

저장
jpa.persist(member)

조회
Member member = jpa.find(memberId)

수정
member.setName("변경이름")

삭제
jpa.remove(member)


EntityManagerFactory는?
하나만 생성하고, 애플리케이션 전체에서 공유한다

EntityManager는?
쓰레드간의 공유는 X, 사용하고 버려야 한다

***
JPA의 모든 데이터 변경은 트랜잭션 안에서 실행한다
***

 

 

 

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

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 {
            // DB에 들어있는 PK가 1L인 Member 객체를 찾는다
            Member findMember = em.find(Member.class, 1L);
            // 1L의 Name을 HelloJPA로 변경
            findMember.setName("HelloJPA");
            // 커밋
            tx.commit();

        } catch (Exception e) {
            // 오류가 날 경우, 롤백한다
            tx.rollback();
        } finally {
            // em은 반드시 닫아준다
            em.close();
        }
        emf.close();
    }
}

 

 

 

 

 

 

JPQL (객체 지향 쿼리 언어)

JPA를 사용하면 Entity 객체를 중심으로 개발한다
검색 쿼리를 어떻게 짤 것인가?
->테이블이 아니라 Entity 객체를 대상으로 검색한다!
모든 DB 데이터를 객체로 변환해서 검색하는 것은 안 된다
애플리케이션이 필요한 Data만 DB에서 불러오려면,
결국 검색 조건이 포함된 SQL이 필요하다

JPQL은 객체를 대상으로 쿼리
SQL은 DB 테이블을 대상으로 쿼리

 

 

// m(Member.class) 라는 Entity(객체)로 찾는 모습
List<Member> result = em.createQuery("select m from Member as m", Member.class)
        // 5번째부터
        .setFirstResult(5)
        // 8개 가져와
        .setMaxResults(8)
        // 리스트로 가져와야함(결과가 많으니)
        .getResultList();

for (Member member : result) {
    System.out.println("member = " + member.getName());
}

 

 

요런 쿼리를 쓴다

 

 

 

 

 


JPA에서 가장 중요한 2가지


1) 객체와 관계형 데이터베이스 매핑하기
2) 영속성 컨텍스트
ㄴ JPA가 내부에서 어떻게 동작하는지 알 수 있다



영속성 컨텍스트란? " Entity를 영구 저장하는 환경 "

INSERT 할 때 쓰는
EntityManager.persist(entity)
ㄴ entity를 영속성 컨텍스트에 저장한다는 뜻


엔티티의 생명주기

비영속 (new / transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

영속 (managed)
영속성 컨텍스트에 관리되는 상태

준영속 (datached)
영속성 컨텍스트에 저장되었다가 분리된 상태

삭제 (removed)
삭제된 상태



비영속?
객체만 생성한 상태, EntityManager와 전혀 관련이 없음

영속?
객체를 생성하고, EntityManager를 가져오고
EntityManager에 객체를 저장한 상태

 

// 비영속
Member member = new Member();
member.setName("HelloJPA");
member.setId(100L);

// 영속, DB에 저장되는 상태는 아님
em.persist(member);
// 준영속, 영속성 컨텍스트에서 분리함
em.detach(member);
// 삭제, DB에서 데이터를 지우겠어
em.remove(member);

// 커밋하는 시점에서 DB에 쿼리가 날라감
tx.commit();

 

 

 

 

영속성 컨텍스트의 이점

1) 1차 캐시

KEY를 @ID로 두고 Entity 객체를 불러옴
1차 캐시에 먼저 저장하기 때문에 DB까지 안 가도 불러올 수 있음
(1차 캐시에 없다면, DB에서 1차캐시에 저장한 다음 불러옴)

 

 

 

// 객체 생성
Member member = new Member();
member.setId(103L);
member.setName("HelloJPA");

// DB에 INSERT 위한 영속성 컨텍스트에 저장
em.persist(member);
// 위에 만든 객체를 불러온다
Member findMember = em.find(Member.class, 103L);

// 1차 캐시에 저장되어 있기 때문에, 쿼리가 동작하기 전에도 불러올 수 있다
System.out.println("findMember.getName() = " + findMember.getName());
System.out.println("findMember.getId() = " + findMember.getId());

tx.commit();

 

 

 

// 영속
Member findMember1 = em.find(Member.class, 103L);
Member findMember2 = em.find(Member.class, 103L);

두번 불러와도

 

쿼리가 한번 동작함

첫 번째에 1차 캐시에서 불러왔기 때문에

두 번째는 DB까지 갈 필요가 없음

 

 

 



2) 동일성 보장

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의
트랜잭션 격리 수준을 DB 가 아닌 애플리케이션 차원에서 제공한다

같은 PK의 데이터를 불러와서 다른 객체에 저장해도
그 두 객체는 동일한 객체로 취급된다

(같은 트랜잭션에서 실행할 경우)


 

// 영속
Member findMember1 = em.find(Member.class, 103L);
Member findMember2 = em.find(Member.class, 103L);

// 두 객체는 같은 객체로 취급된다
System.out.println("result = "+(findMember1 == findMember2));

 

 

 

 

 



3) 엔티티 등록할 때, 트랜잭션을 지원하는 쓰기 지원


쓰기 지연 SQL 저장소에 있던 SQL들이 commit 순간, DB로 날라감
그 전까진 쓰기 지연 SQL 저장소에 SQL들이 담겨짐

 

잘 활용하면 성능이 더 조아질 수 있어용

 

Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");

// 여기서는 쓰기 지연 SQL 저장소에 저장만 해둠
em.persist(member1);
em.persist(member2);

// commit 시점에 DB 들어감
tx.commit();

 

 

 

 



4) 엔티티 수정할 때, 변경 감지 가능

자바 컬렉션 다루듯이 변경이 가능
List에 넣은 객체의 값을 변경하고도 다시 넣지 않아도 되는 것처럼
find()로 가져온 객체에 setName() 하더라도 다시 넣지 않아도 됨,
set 하면 바로 바뀜

Member member1 = em.find(Member.class,150L);
// setName만 해줘도, 자동으로 update쿼리 실행
member1.setName("ZZZZZ");

tx.commit();

 

 

 

업데이트 쿼리가 실행되고

 

 

ID 150인 행의 이름이 바뀌어있다

 

 

 



어떻게 가능할까?

commit을 실행 하면
1) flush()
2) 1차 캐시 안에 있는 엔티티와 스냅샷(값을 읽어온 최초 시점)을 비교함
3) 어? 바꼈네? -> 업데이트 쿼리를 생성 -> DB에 반영하고 commit함

 

 

 

 

5) 엔티티 삭제
걍 조회하고, remove 실행하면 commit 시점에 삭제함ㅎㅎ

 

 

 

 

 



플러시?
ㄴ 영속성 컨텍스트의 변경 내용(쿼리)을 DB에 반영하는 것

- 변경을 감지합니다
- 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 DB에 저장
(등록, 수정, 삭제 쿼리)


영속성 컨텍스트를 플러시하는 방법
- 직접 호출 : em.flush() (테스트 할 때 사용, 거의 쓸 일 없긴 해요)
- 자동 호출 : 트랜잭션 commit
- 자동 호출 : JPQL 쿼리 실행


 

직접 호출 : flush

 

Member member1 = new Member(200L, "member200");
em.persist(member1);

// 쿼리를 미리 보고 싶어요
em.flush();

System.out.println("================");

tx.commit();

 

==== 전에 쿼리 실행된다

 

 

플러쉬를 해도 1차 캐시는 다 유지됩니다

쓰기 지연 SQL 저장소에 있는 것들을 데이터에 반영하는 동작일 뿐입니당

 

 

 



JPQL 쿼리 실행시 플러시가 자동으로 호출되는 이유?


persist를 해놓고
중간에 JPQL select문을 실행하면 일단 고거 flush함

 

 

 




플러시는?!


영속성 컨텍스트를 비우는 것이 아니에요
ㄴ 변경 내용을 DB에 동기화 하는 것뿐

트랜잭션이라는 작업 단위가 중요해요
ㄴ 커밋 직전에만 동기화하면 됨

 

 

 

 

------------------------


준영속 상태

영속상태에서 준영속상태로 갈 수 있다
persist 뿐만 아니라 find 같은 1차 캐시에 있는 상태도 영속상태이다
영속 상태의 엔티티가 영속성 컨텍스트에서 분리되는 것

 

 

 

detach

// 영속 상태
Member member1 = em.find(Member.class,150L);
member1.setName("ZZZZZ");

// 영속 상태에서 분리함, JPA에서 관리하지 않음
// commit되지 않음
em.detach(member1);

tx.commit();

 

 

 

select문만 나오고 update 쿼리는 없다

 

 

 

 

 

 

clear

 

 

// 영속 상태
Member member1 = em.find(Member.class,150L);
member1.setName("ZZZZZ");

// 영속 상태에서 분리함, JPA에서 관리하지 않음
// 엔티티에 있는 영속성 컨텍스트를 전부 분리함
em.clear();

tx.commit();

 

 

 

select문만 나오고 update 쿼리는 없다

 

 

 

 

close() 영속성 컨텍스트를 종료

 

 

 

 

 

 

-----------------------------

 

 

정리)

 

JPA에서 가장 중요한 2가지는?

1) 객체와 DB 매핑

2) 영속성 컨텍스트

 

 

 

 

영속성 컨텍스트는?

엔티티를 저장하는 공간

엔티티매니저를 생성하면, 영속성컨텍스트가 드러갑니다

-> 스프링이랑 들어가면 살짝 달라짐

 

 

 

 

엔티티는 생명주기가 있어요

비영속, 영속, 준영속, 삭제

 

 

 

영속성 컨텍스트의 이점

1) 1차 캐시 -> 한번 조회햇던거 또 조회하면 DB까지 안들림

2) 동일성 보장 -> 객체 == 객체 같아요

3) 트랜잭션을 지원하는 쓰기 지연 / 버퍼링했다가 -> 한번에 쫙 커밋

4) 변경 감지해서 update 자동

5) 지연 로딩 -> 쿼리를 나중에 날리기 가능~

 

 

 

플러시

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

영속성 컨텍스트를 비우지 않음

** 트랜잭션이라는 작업 단위가 중요해요 -> 커밋 직전에만 동기화하세용

ㄴ 처음에 설계할 때, 영속성 컨텍스트랑 트랜잭션 주기를 맞춰서 설계해야해요

 

 

 

728x90