[JPA] JPQL, 영속성 컨텍스트

보람·2023년 4월 30일
0

Spring

목록 보기
11/18

1. 📇 JPQL

JPQL이란?

JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공

  • JPA는 쿼리를 짤 때 table을 대상으로 쿼리를 짜지 않고 엔티티 객체를 중심으로 개발
    • 문제점 : 검색 쿼리, 테이블이 아닌 엔티티 객체를 대상으로 검색
    • 모든 DB데이터를 객체로 변환해서 검색하는 것은 불가능
    • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색조건이 포함된 SQL문이 필요

      해결

      • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
    • SQL : 데이터베이스 테이블을 대상으로 쿼리
    • JPQL : 엔티티 객체를 대상으로 쿼리, 객체지향 SQL
      • SQL과 문법이 유사
        • SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원

💡 예시

	List<Member> result = em.createQuery("select m from Member as m", Member.class)
							.setFirstResult(5)	// 5번 부터
							.setMaxResults(10)  // 10개 가지고 오기
							.getResultList();
			
	for(Member member : result) {
		System.out.println("member.name = " + member.getName());
	}

👀 JPA에서 중요한 2가지

1. 객체와 관계형 데이터베이스 매핑(Object Relational Mapping) - ORM

2. 영속성 컨텍스트

2. 영속성 컨텍스트

(1) 영속성 컨텍스트

  • JPA를 이해하는 데 가장 중요한 용어
  • "엔티티(ENTITY)를 영구 저장하는 환경(공간)"이라는 뜻의 논리적인 개념

1) ENTITY

  • JPA는 데이터와 연관된 객체들을 Entity로 분류하여 관리
  • 따라서 데이터를 읽거나, 조회하거나 쓰는 내용들이 Entity를 통해서 이루어 짐

이러한 엔티티들을 JPA는 어떠한 곳에 저장하는데, 이 영역을 영속성 컨텍스트

2) ENTITY MANAGER

영속성 컨텍스트는 엔티티 매니저를 통해서 접근

  • JPA는 스레드가 하나 생성될 때 마다(매 요청마다) EntityManagerFactory에서 EntityManager를 생성
  • EntityManager는 내부적으로 DB 커넥션 풀을 사용해서 DB에 붙음
  • 커넥션 풀에서 커넥션을 꺼내 DB에 데이터를 넣거나 조회하는 작업 등을 수행
  • persist, findBy와 같은 메소드를 가지고 이 영속성 컨텍스트에서 엔티티들을 꺼내오거나 넣게 됨

EntityManager.persist(entity); cf. em.persist(member);
"entity"에 들어가는 객체를 DB에 저장하는 느낌
실제로는 DB에 저장하는 것 ❌ 영속성 컨텍스트에 저장
DB 저장은 tx.commit();에서

3) 엔티티 매니저 vs 영속성 컨텍스트

  • 엔티티 매니저를 통해서 영속성 컨텍스트라는 공간에 접근
  • DB에 가기전에 어떤 공간을 들러야 하는데 엔티티 매니저로 가능하다는 뜻
  • 영속성 컨텍스트는 논리적인 개념이므로 눈에 보이지는 않는다.
  • 엔티티 매니저와 영속성 컨텍스트는 1:1 대응

(2) 엔티티 생명주기

  • 비영속 (new/transient)

    • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
      // 객체를 생성만 한 상태(비영속)
      Member member = new Member();
      member.setId("member1");
      member.setUsername("회원1");
  • 영속 (managed)

    • 영속성 컨텍스트에 저장된 상태
    • 영속성 컨텍스트에 의해 관리되는 상태
    • DB에 저장 ❌
      // 객체를 생성한 상태(비영속)
      Member member = new Member();
      member.setId("member1");
      member.setUsername("회원1");
    
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
    
      // 객체를 저장한 상태(영속)
      em.persist(member);
    ``
  • 준영속 (detached)

    • 영속성 컨텍스트에 저장되었다가 분리된 상태
      // 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
      em.detach(member);
      em.clear(member);
      em.close(member);
    • .merge() : 분리되었다가 다시 영속성 컨텍스트로
  • 삭제 (removed)

    • 삭제된 상태 = 영속성 컨텍스트가 비워진 상태
      // 객체를 삭제한 상태
      em.remove(member);
    • .persist() : 다시 영속성 컨텍스트로
  • flush() : 영속성 컨텍스트에서 db에 들어가기 위해사용

3. 영속성 컨텍스트의 장점

  • 애플리케이션과 DB사이에 왜 영속성 컨텍스트가 필요할까?
    • 1차 캐시
    • 동일성(identity)보장
    • 트랜잭션을 지원하는 쓰기 지연(Transactional write-behind)
    • 변경감지(Dirty Checking)
    • 지연로딩(Lazy Loading)

(1) 1차 캐시

  • 영속성 컨텍스트 내부에 1차 캐시 존재
  • 영속성 컨텍스트 내에 저장되어 있는 값은 별도의 새로운 트랜잭션 없이 1차 캐시 영역 내에서 가져와 트랜잭션을 절약할 수 있음
  • 엔티티를 영속성 컨텍스트에 저장
	Member member = new Member();
	member.setId("member1");
	member.setUsername("회원1");
    
    // 객체를 영속성 컨텍스트에 저장(영속)
	em.persist(member);
  • key : @Id로 선언한 필드 값, value : 해당 엔티티 자체로 캐시에 저장
  • 영속성 컨텍스트와 식별자 값
    • 엔티티를 식별자값(@id로 테이블의 기본 키와 매핑한 값)으로 구분
    • 영속 상태는 식별자값이 반드시 있어야 한다.
    • 식별자 값이 없으면 예외 발생
  • 영속성 컨텍스트와 데이터베이스 저장
    • JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 DB에 반영
    • 플러쉬(flush)

이때, find()가 일어난다면?

 Member findMember = em.find(Member.class, "Member1"); 

⭕ 1차 캐시에 데이터가 있을 때

  • find()가 일어나는 순간, 엔티티 매니저 내부의 1차 캐시를 먼저 찾음
  • 1차 캐시에 엔티티가 존재하면 바로 반환 -> DB 안 들림

❌ 1차 캐시에 데이터가 없을 때

  • 1차 캐시에 없음 -> DB 들림
  • DB에서 꺼냄 -> 1차 캐시에 저장 -> MEMBER2 반환
  • 이후에 member2를 조회 -> 1차 캐시에 있는 member2가 반환

(2) 동일성(identity)보장

같은 데이터를 2번 조회할 경우

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

System.out.println(a == b) // true 
  • 같은 호출을 반복해도 1차 캐시에 있는 같은 엔티티 반환하여 동일성을 보장
  • Mybatis는 동일성 보장이 안됨

(3) 트랜잭션을 지원하는 쓰기 지연(transactional write-behind) - 엔티티 등록

EntityManager em = EntityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션

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

// 비영속
Member memberA = new Member();
memberA.setId("member1");
memberA.setUsername("홍길동");

Member memberB = new Member();
memberB.setId("member1");
memberB.setUsername("홍길동");

// 영속 : 여기까지 Insert SQL을 데이터베이스에 보내지 않는다
em.persist(memberA);
em.persist(memberB);

// 엔티티 등록 : commit하는 순간 데이터베이스에 Insert SQL을 보냄
tx.commit();
  • persist()가 일어나면
  • 엔티티들을 1차 캐시에 저장 + 논리적으로 쓰기 지연 SQL 저장소 라는 곳에 INSERT 쿼리들을 저장
  • DB에 바로 넣지 않고 기다림
  • commit()할 때, DB에 쿼리들을 보냄
  • 쿼리들을 DB에 보내는 동작이 flush()

여러 개의 엔티티를 생성하고 persist를 하더라도 commit()을 하기 전에는 데이터베이스에 저장❌ --> 쓰기 지연

(4) 변경감지(Dirty Checking)

📌 엔티티 수정 코드

EntityManager em = EntityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션

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

// 조회 
Member member A = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

// em.update(member); 이런 코드 없이도 update 가능
tx.commit();
  • 엔티티 수정 -> update(), persist() 필요없음

  • 데이터만 set하고 트랜잭션을 커밋하면 자동으로 업데이트 쿼리

  • HOW? 변경 감지(Dirty Checking)를 통해

  • persist()로 1차 캐시에 저장할 때 동시에 스냅샷 필드도 저장

  • commit()또는 flush() 실행 시, 엔티티와 스냅샷을 비교해서 변경사항이있으면 UPDATE SQL을 알아서 만들어서 DB에 저장

4. 플러시

영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것

⚡ flush

플러시가 발생하면

  • 변경을 감지(Dirty Checking)
  • 수정된 엔티티를 쓰기 지연 SQL 저장소(transactional write-behind)에 등록
  • 쓰기 지연 SQL 저장소(transactional write-behind)의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
  • 플러시가 발생한다고 커밋❌ 플러시 다음에 커밋

영속성 컨텍스트를 플러시 하는 법

  • 직접 호출 : em.flush()
  • 자동 호출 : tx.commit()(트랜잭션 커밋), JPQL 쿼리 실행

플러시의 역할

  • 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
  • 플러시가 동작할 수 있는 이유는 데이터베이스 트랜잭션이라는 작업 단위(개념)가 있기 때문 → 커밋 직전에만 동기화하면 됨
  • JPA는 기본적으로 데이터를 맞추거나 동시성에 관련된 것들은 데이터베이스 트랜잭션에 위임

5. 준영속상태

  • 영속상태
    • 1차 캐시에 올라간 상태
    • 엔티티 매니저가 관리하는 상태
    • em.persist()로 영속성 컨텍스트에 저장한 상태
    • em.find()로 조회를 할 때, 영속성 컨텍스트 1차 캐시에 없어서 DB에서 조회해와서 1차 캐시에 저장한 상태
  • 준영속상태
    • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)된 상태
    • 영속성 컨텍스트가 제공하는 기능을 사용하지 못함. 쿼리 안나감.

준영속 상태로 만드는 방법

  • em.detach(entity)
    • 특정 엔티티만 준영속 상태로 전환
  • em.clear()
    • 영속성 컨텍스트를 완전히 초기화
    • clear는 테스트 케이스 작성시에 도움
  • em.close()
    • 영속성 컨텍스트를 종료
profile
안녕하세요, 한보람입니다.

0개의 댓글