ORM JPA2

유요한·2023년 12월 1일
0

JPA

목록 보기
2/10
post-thumbnail

엔티티

엔티티란 데이터베이스의 테이블에 대응하는 클래스라고 생각하면 됩니다. @Entity가 붙은 클래스는 JPA에서 관리하며 엔티티라고 합니다. 데이터베이스에 item 테이블을 만들고, 이에 대응되는 Item 클래스를 만들어서 @Entity 어노테이션을 붙이면 이 클래스가 엔티티가 되는 것입니다. 클래스 자체나 생성한 인스턴스도 엔티티라고 합니다.

엔티티

엔티티란 데이터베이스의 테이블에 대응하는 클래스라고 생각하면 됩니다. @Entity가 붙은 클래스는 JPA에서 관리하며 엔티티라고 합니다. 데이터베이스에 item 테이블을 만들고, 이에 대응되는 Item 클래스를 만들어서 @Entity 어노테이션을 붙이면 이 클래스가 엔티티가 되는 것입니다. 클래스 자체나 생성한 인스턴스도 엔티티라고 합니다.

엔티티 매니저 팩토리

엔티티 매니저 팩토리는 엔티티 매니저 인스턴스를 관리하는 주체입니다. 애플리케이션 실행 시 한 개만 만들어지며 사용자로부터 요청이 오면 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성합니다.

엔티티 매니저

엔티티 매니저는 이름 그대로 엔티티를 관리하는 매니저입니다. 엔티티 매니저는 데이터베이스에 접근해서 CRUD 작업을 수행합니다. Spring Data JPA를 사용하면 레포지토리를 사용해서 데이터베이스에 접근합니다. 엔티티 매니저는 엔티티 매니저 팩토리가 만듭니다. 엔티티 매니저 팩토리는 데이터베이스에 대응하는 객체로서 스프링 부트에서는 자동 설정 기능이 있기 때문에 application.properties에서 작성한 최소한의 설정으로도 동작하지만 JPA의 구현체 중 하나인 하이버네이트에서는 persistence.xml이라는 설정 파일을 구성하고 사용해야 하는 객체입니다. 엔티티 매니저란 영속성 컨텍스트에 접근하여 엔티티에 대한 데이터베이스 작업을 제공합니다. 내부적으로 데이터베이스 커넥션을 사용해서 데이터베이스에 접근합니다. 엔티티 매니저의 몇 가지 메소드를 살펴보겠습니다.

  1. find() 메소드 : 영속성 컨텍스트에서 엔티티를 검색하고 영속성 컨텍스트에 없을 경우 데이터베이스에서 데이터를 찾아 영속성 컨텍스트에 저장합니다.

  2. persist() 메소드 : 엔티티를 영속성 컨텍스트에 저장합니다.

  3. remove() 메소드 : 엔티티 클래스를 영속성 컨텍스트에서 삭제합니다.

  4. flush() 메소드 : 영속성 컨텍스트에 저장된 내용을 데이터베이스에 반영합니다.

주의

  • 엔티티의 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유
  • 엔티티 매니저는 쓰레드간의 공유X (사용하고 버려야 한다.)
  • JPA는 모든 데이터 변경은 트랜잭션 안에서 실행

엔티티 생명주기

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

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

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

  • 삭제(removed)
    삭제된 상태


JPQL

만약에 나이가 18살 이상인 회원을 모두 검색하고 싶다면 이 때는 JPQL을 쓰는 것이다.

JPQL은 JPA Query Language의 줄임말로 JPA에서 사용할 수 있는 쿼리 의미합니다. JPQL의 문법은 SQL과 매우 비슷해서 데이터베이스 쿼리에 익숙한 사람들은 어렵지 않게 사용할 수 있습니다.

SQL과의 차이점은 SQL에서는 테이블이나 칼럼의 이름을 사용하는 것과 달리 JPQL은 엔티티 객체를 대상으로 수행하는 쿼리이기 때문에 매핑된 엔티티의 이름과 필드의 이름을 사용하는 점입니다.

  • JPA를 사용하면 엔티티 객체를 중심으로 개발

  • 문제는 검색 쿼리

  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색

  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능

  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요

  • 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리

  • SQL을 추상화해서 특정 데이터베이스 SQL에 의존X

  • JPQL을 한마디로 정의하면 객체 지향 SQL


영속성 컨텍스트

JPA를 이해하기 위해서는 영속성 컨텍스트를 이해하는 것이 가장 중요합니다. 영속성 컨텍스트는 애플리케이션과 데이터베이스 사이에서 엔티티와 레코드의 괴리를 해소하는 기능과 객체를 보관하는 기능을 수행합니다. 엔티티 객체가 영속성 컨텍스트에 들어오면 JPA는 엔티티 객체의 매핑 정보를 데이터베이스에 반영하는 작업을 수행합니다. 이처럼 엔티티 객체가 영속성 컨텍스트에 들어와 JPA의 관리 대상이 되는 시점부터는 해당 객체를 영속 객체라고 부릅니다. 엔티티를 영구 저장하는 환경으로 엔티티 매니저를 통해 영속성 컨텍스트에 접근합니다.

영속성 컨텍스트는 세션 단위의 생명주기를 가집니다. 데이터베이스에 접근하기 위한 세션이 생성되면 영속성 컨텍스트가 만들어지고, 세션이 종료되면 영속성 컨텍스트도 없어집니다. 엔티티 매니저는 이러한 일련의 과정에서 영속성 컨텍스트에 접근하기 위한 수단으로 사용됩니다.

생명주기내용
비영속(new)new 키워드를 통해 생성된 상태로 영속성 컨텍스트와 관련이 없는 상태
영속- 엔티티가 영속성 컨텍스트에 저장된 상태로 영속성 컨텍스트에 의해 관리되는 상태
- 영속 상태에서 데이터베이스에 저장되지 않으며, 트랜잭션 커밋 시점에 데이터베이스에 반영
준영속 상태(detached)영속성 컨텍스트에 엔티티가 저장되었다가 분리된 상태
삭제 상태(removed)영속성 컨텍스트와 데이터베이스에서 삭제된 상태

설명만 보고는 어떻게 동작하는지 알기 어려우니 상품 엔티티를 만들어서 영속성 컨텍스트에 저장후 데이터베이스 반영하는 코드를 살펴보겠습니다.

Item item = new Item();  → ①
item.setItemNum("테스트 상품");

EntityManager em = entityManagerFactory.createEntityManger(); → ②

EntityTransaction transaction = em.getTransaction() → ③
transaction.begin();

em.persistence(item);	→ ④

transaction.commit();	→ ⑤

em.close();	→ ⑥
emf.close() → ⑦

① 영속성 컨텍스트에 저장할 상품 엔티티를 하나 생성합니다. new 키워드를 통해 생성했으므로 영속성 컨텍스트와 관련이 없는 상태입니다.

② 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성합니다.

③ 엔티티 매니저는 데이터 변경 시 데이터의 무결성을 위해 반드시 트랜잭션을 시작해야 합니다. 여기서의 트랜잭션도 데이터베이스의 트랜잭션과 같은 의미입니다.

트랜잭션이란?

  • 트랜잭션은 작업의 완전성을 보장해주는 것이다.

  • 논리적인 작업 셋을 모두 완벽하게 처리하거나 또는 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능

  • 트랜잭션의 4가지 특성(ACID) : 원자성, 일관성, 고립성, 지속성

트랜잭션

데이터베이스의 상태를 변경시키기 위해 수행하는 작업이다. 이러한 트랜잭션은 상황에 따라 여러개 만들어질 수 있다.

트랜잭션의 특징

④ 생성한 상품 엔티티가 영속성 컨텍스트에 저장된 상태입니다. 여기까지는 데이터베이스에 Insert SQL을 보내지 않은 단계입니다.

⑤ 트랜잭션을 데이터베이스에 반영합니다. 이때 영속성 컨텍스트에 저장된 상품 정보가 데이터베이스 Insert 되면서 반영됩니다.

⑥, ⑦ 엔티티 매니저와 엔티티 매니저 팩토리의 close() 메소드를 호출해 사용한 자원을 반환합니다.

영속성 컨텍스트 사용시 이점

JPA는 왜 이렇게 영속성을 사용할까? 그 이유는 애플리케이션과 데이터베이스 사이에 영속성 컨텍스트라는 중간 계층을 만들었기 때문입니다. 이렇게 중간 계층을 만들면 버퍼링, 캐싱 등을 할 수 있습니다.

1차 캐시

영속성 컨텍스트에는 1차 캐시가 존재하며 Map(Key, Value)로 저장됩니다. entityManager.find() 메소드 호출 시 영속성 컨텍스트의 1차 캐시를 조회합니다. 엔티티가 존재할 경우 해당 엔티티를 반환하고 엔티티가 없으면 데이터베이스에서 조회 후 1차 캐시에 저장 및 반환합니다.

  • find() : 영속성 컨텍스트에서 엔티티를 검색하고 영속성 컨텍스트에 없을 경우 데이터베이스에서 데이터를 찾아 영속성 컨텍스트에 저장
  • persist() : 엔티티를 영속성 컨텍스트에 저장
  • remove() : 엔티티 클래스를 영속성 컨텍스트에서 삭제
  • flush() : 영속성 컨텍스트에 저장된 내용을 데이터베이스에 반영

동일성(identity) 보장

하나의 트랜잭션에서 같은 키 값으로 영속성 컨텍스트에 저장된 엔티티 조회 시 같은 엔티티 조회를 보장합니다. 바로 1차 캐시에 저장된 엔티티를 조회하기 때문에 가능합니다.

트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

영속성 컨텍스트에 쓰는 지연 SQL 저장소가 존재합니다. entityManager.persist()를 호출하면 1차 캐시에 저장되는 것과 동시에 쓰기 지연 SQL 저장소에 SQL문이 저장됩니다. 이렇게 SQL을 쌓아두고 트랜잭션을 커밋하는 시점에 저장된 SQL문들이 flush되면서 데이터베이스에 반영됩니다. 이렇게 모아서 보내기 때문에 성능에서 이점을 볼 수 있습니다.

변경 감지(Dirty Checking)

JPA는 1차 캐시에 데이터베이스에서 처음 불러온 엔티티의 스냅샷을 갖고 있습니다. 그리고 1차 캐시에 저장된 엔티티와 스냅샷을 비교 후 변경 내용이 있다면 UPDATE SQL문을 쓰기 지연 SQL 저장소에 담아둡니다. 그리고 데이터베이스에 커밋 시점에 변경 내용을 자동으로 반영합니다. 즉, 따로 update문을 호출할 필요가 없습니다.

em.update(member) 같은 코드를 작성하지 않아도 일반적인 자바처럼 xxx.setUsername("hi") 이런식으로 하면 DB에도 변경이 됩니다. 이게 가능한 이유는 영속 컨텍스트 때문입니다.

flush()가 이루어지면 엔티티와 스냅샷을 비교합니다. 스냡샷이란 값을 읽어온 시점, 즉 최초 시점을 스냅샷으로 떠놓는 겁니다. 만약에 엔티티가 변경이 되면 전체적으로 비교를 했을 때 차이가 생기게 되는데 그러면 update query를 SQL 저장소에 생성을 합니다. 그리고 update query를 DB에 반영을 하고 commit을 하게 됩니다.

플러시(flush)

영속성 컨텍스트의 변경내용을 데이터 베이스에 저장

플러시(flush) 발생

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

영속성 컨텍스트를 플러시(flush)하는 방법

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

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

플러시는 영속성 컨텍스트를 비우지 않는다. 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화한다. 트랜잭션이라는 작업 단위가 중요 → 커밋 직전에만 동기화하면 된다.

Spring Data JPA와 JPA는 무슨 차이일까?

Spring Data JPA는 JPA 구현체를 스프링에 맞춰 더욱 쉽게 사용할 수 있도록 도와주는 모듈입니다. JPA를 쓴다는 것은 위와 같은 큰 아키텍처를 이해하고 관련 API를 눈감고도 쓸 수 있는 수준이 되어야 한다는 것입니다.

준영속 상태

  • 영속 → 준영속
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
  • 영속성 컨텍스가 제공하는 기능을 사용 못함

준영속 상태로 만드는 방법

  • em.detach(entity)
    특정 엔티티만 준영속 상태로 전환
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager entityManager = emf.createEntityManager();

        EntityTransaction tx = entityManager.getTransaction();
        tx.begin();

        try {
            // 영속
            Member member = entityManager.find(Member.class, 150L);
            member.setName("ZZZ");

            // 준영속
            // 영속성 컨텍스트에서 관리하지 말라고 끊어낸것
            entityManager.detach(member);

            // 아무일도 일어나지 않는데 jpa에서 관리하지 않기 때문이다.
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            entityManager.close();
        }

        emf.close();
    }
}
  • em.clear()
    영속성 컨텍스트를 완전히 초기화
package com.example.jpa.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 entityManager = emf.createEntityManager();

        EntityTransaction tx = entityManager.getTransaction();
        tx.begin();

        try {
            // 영속
            Member member = entityManager.find(Member.class, 150L);
            member.setName("ZZZ");

            entityManager.clear();

            // 아무일도 일어나지 않는데 jpa에서 관리하지 않기 때문이다.
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            entityManager.close();
        }

        emf.close();
    }
}
  • em.close()
    영속성 컨텍스트를 종료

profile
발전하기 위한 공부

0개의 댓글