인프런 김영한 강사님 강의를 듣고 정리한 내용입니다.

JPA

Java Persistence Api
자바 진영의 ORM 기술 표준이다.

ORM

Object-Relational Mapping

  • 객체는 객체대로 설계
  • 관계형 데이터베이스는 관계형 데이터베이스대로 설계
  • ORM 프레임워크가 중간에서 매핑
  • 대중적인 언어에는 대부분 ORM 기술이 존재

JPA는 애플리케이션과 JDBC 사이에서 동작한다.

JPA 구현체로는 하이버네이트, EclipseLink, DataNucleus 등이 있으며, 주로 하이버네이트를 사용한다.

JPA를 사용하는 이유

  • SQL 중심의 개발에서 객체 중심으로 개발
  • 생산성 향상
  • 유지보수의 용이성
  • 패러다임의 불일치 해결
  • 성능 향상
  • 데이터 접근 추상화와 벤더 독립성
  • 자바 ORM 기술의 표준임

JPA 기본 개념

Entity

엔티티는 JPA가 관리할 객체이다.
클래스 위에 @Entity를 붙여서 엔티티로 선언하며, 반드시 primary key가 필요하다. 그래서 pk로 선언하는 필드 위에 @Id를 붙인다. 만약 surrogate key로써 DB가 생성해주는 값을 사용하고 싶다면, pk 필드 위에 @Generated를 함께 붙여준다.

엔티티 매니저 팩토리

엔티티 매니저를 생성하고 관리한다. 엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유하도록 한다. 스프링 사용 시에 엔티티 매니저 팩토리는 자동으로 빈으로 등록되고, 엔티티 매니저의 의존관계 주입에서 스프링이 관리하여 엔티티를 생성해준다.

엔티티 매니저

엔티티를 관리한다. 엔티티 매니저는 스레드간 공유를 해서는 안되며, 하나의 스레드에서 사용 후 버려야한다. 또한 JPA의 모든 데이터 변경은 트랜잭션 안에서 실행된다. 엔티티 매니저는 앞서 말한 것과 같이 final로 선언하면 스프링이 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성하고, 이를 생성자를 통해 DI 해준다.

JPQL

JPA에서 사용하는 객체 지향 쿼리 언어이다. SQL을 추상화한 언어로 SQL과 문법이 유사하다. 하지만 SQL과 달리 엔티티 객체를 대상으로 쿼리를 해야한다. (SQL은 DB 테이블 대상으로 쿼리)

영속성 컨텍스트

영속성 컨텍스트는 논리적인 개념으로, 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있다. 영속성 컨텍스트는 엔티티를 기억하고 관리하는 역할을 한다.

영속성 컨텍스트 상세

영속성 컨텍스트는 엔티티를 기억하고 관리한다. 영속성 컨텍스트는 트랜잭션의 시작과 함께 초기화되고, 트랜잭션이 끝날 때 함께 사라진다.

영속성 컨텍스트 역할과 장점

  1. 1차 캐싱
  2. 동일성 보장
  3. 쓰기 지연
  4. Dirty Checking
  5. 지연 로딩

1차 캐싱

DB에서 매번 쿼리를 해서 값을 가져오는 것은 비용이 많이 든다. 그래서 엔티티 매니저로 엔티티를 조회할 때, 영속성 컨텍스트의 1차 캐시에 조회한 엔티티의 데이터가 저장된다. 그래서 다음번에 같은 엔티티를 조회하면, 1차 캐시로부터 값을 가져올 수 있다.

데이터 조회 시, 첫번째로 영속성 컨텍스트의 1차 캐시를 확인함.
두번째로 DB에 쿼리를 해서 데이터를 읽어들임.

동일성 보장

1차 캐시 덕분에 영속성 컨텍스트에 저장된 엔티티는 여러번 조회하더라도 항상 동일한 객체를 반환한다.

동등성 : 두 객체의 값이 같음
동일성 : 두 객체의 주소가 같음 (완전히 동일한 객체)

쓰기 지연

데이터 삽입, 삭제, 수정이 일어날 때, 매번 SQL을 수행하지 않는다.
대신 모든 트랜잭션이 끝난 후 commit될 때, 필요에 따라 쿼리를 한다.
트랜잭션이 수행되는 동안에는 영속성 컨텍스트에 값을 저장하거나 변경하여 DB에서 읽는 것처럼 사용한다.

Dirty Checking

영속성 컨텍스트에 저장된 엔티티들에 대해서 flush()를 했을 때 Dirty Checking을 수행한다. flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것이다.

다시 말해, Dirty Checking 이란 flush()를 했을 때 1차 캐시에 저장된 값들이 변경되었을 경우, 이를 감지하여 DB의 값에 반영시키는 것이다.

Dirty Checking에 사용하기 위해서 엔티티를 최초 조회했을 때의 값을 저장한 스냅샷(복사본)을 영속성 컨텍스트에 저장해둔다.

flush()가 일어날 때마다 Dirty Checking이 수행되지만, 영속성 컨텍스트는 유지된다. (영속성 컨텍스트의 초기화는 clear()를 통해서 할 수 있다.)

일반적인 애플리케이션에서는 트랜잭션이 끝날 때(commit 혹은 rollback),
자동으로 Dirty Checking
을 하게 된다. (flush() 수동호출 안해도 됨)

혹은 JPQL 쿼리 실행 시에도, 쿼리 실행 직전에 플러시를 자동 호출한다.

지연 로딩

연관관계에 있는 엔티티까지 조회할 필요가 없을 때 지연 로딩을 사용할 수 있다. fetch type이 lazy인 경우에 지연 로딩이 수행된다. 지연 로딩으로 설정한 경우에는 연관관계에 있는 엔티티에 실제 값이 들어가거나 null이 들어가는 대신에 프록시가 들어간다.

그리고 해당 연관관계 필드를 실제로 조회할 때(연관관계 필드의 메서드 호출) 프록시에서 타겟을 호출하여 값을 조회하게 된다. (단, 일반적인 join으로 값을 읽을 경우에는 연관관계에 있는 엔티티는 영속성 컨텍스트에 저장되지 않는다. 연관관계 엔티티까지 영속화하고 싶다면 fetch join을 사용해야한다.)

N+1 문제 참고 : https://velog.io/@suhsein/JPA-N1-%EB%AC%B8%EC%A0%9C-Fetch-join-EntityGraph

엔티티의 생명 주기

엔티티의 상태는 영속성 컨텍스트와 관련하여 크게 4가지로 나눌 수 있다.

  1. 비영속 상태 Transient/New
    새로 생성된 상태. 영속성 컨텍스트와 전혀 관계가 없음
  2. 영속 상태 Managed
    영속성 컨텍스트 내부에 존재하는 상태. 엔티티가 영속성 컨텍스트 내부에 있으면, 영속성 컨텍스트에 의해 관리된다.
  3. 준영속 상태 Detached
    영속성 컨텍스트 내부에 저장되었다가 현재는 분리된 상태. DB에서 확인이 가능한 데이터이다.
  4. 삭제 removed
    삭제된 상태. 영속성 컨텍스트로부터 삭제 명령을 통해 삭제된 상태이다.

영속 상태 엔티티와, 삭제 엔티티는 트랜잭션이 끝날 때 자동으로 수행되는 flush()의 영향을 받는다.
영속 상태 엔티티는 Dirty Checking을 하게 된다. 그리고 삭제 엔티티는 DB에서 삭제된다.

준영속 상태

영속 상태에 있는 엔티티를 강제로 준영속 상태로 만들려면 detach()를 사용하면 된다. 세션이 종료되는 경우(트랜잭션이 끝나서 영속성 컨텍스트가 사라짐)에는 자동으로 준영속 상태가 된다.

준영속 상태일 때는 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다. 특히 동일성 보장이 안되고, Dirty Checking 이 안된다는 점에 주의해야 한다.

준영속 엔티티의 수정

영속 상태의 엔티티는 객체를 수정하는 것처럼 수정하고, Dirty Checking을 통해 반영시킬 수 있다.

준영속 엔티티를 수정하려면 두가지 방법이 있다.

  1. Dirty Checking
  2. merge

두 방법 모두 준영속 엔티티를 직접 수정하는 것은 아니며, 준영속 엔티티와 pk가 같은 영속 엔티티를 조회하여 수정한다.

주의 : 준영속 엔티티와, 준영속 엔티티 pk로 조회한 영속 엔티티 사이에는 동일성이 보장되지 않는다. 영속 엔티티의 값을 수정한 후에 준영속 엔티티를 수정하더라도 둘은 다른 객체이기 때문에 영속 엔티티에는 아무런 영향을 미치지 않는다.
(준영속일 경우에는 영속성 컨텍스트의 관리를 받지 못하고, 동일성이 보장되지 않기 때문이다.)

먼저 파라미터로 수정된 값이 담긴 준영속 엔티티가 들어온다고 가정하자.

준영속 엔티티 수정 - Dirty Checking

  1. 준영속 엔티티의 pk를 사용해 엔티티를 조회한다.
    먼저 1차 캐시를 조회하고, 있다면 영속 엔티티를 반환한다.
    없으면 DB를 조회해 영속성 컨텍스트에 저장하고, 영속 엔티티를 반환한다.

  2. 반환된 영속 엔티티를 수정한다.
    파라미터로 들어온 준영속 엔티티의 값을 이용하여 setter 혹은 수정 메서드로 영속 엔티티를 수정할 수 있다. 원하는 속성(필드)만 변경이 가능하다.

  3. 트랜잭션 종료 시 Dirty Checking을 통해서 수정한 영속 엔티티의 값을 DB에 반영시킬 수 있다.

준영속 엔티티 수정 - merge

  1. 준영속 엔티티를 merge()의 파라미터로 넘겨주고, merge를 수행한다.
    다음은 merge 수행 과정이다.

  2. 준영속 엔티티의 pk를 사용해 엔티티를 조회한다.
    먼저 1차 캐시를 조회하고, 있다면 영속 엔티티를 가져온다.
    없으면 DB를 조회해 영속성 컨텍스트에 저장하고, 영속 엔티티를 가져온다.

  3. 파라미터로 들어온 준영속 엔티티의 모든 값을 조회된 영속 엔티티에 적용한다. 엔티티의 특정 필드만 적용되는 것이 아니라 모든 필드가 덮어 씌워진다.

  4. merge가 완료되었고, 값이 수정된 영속 엔티티를 반환한다.

주의 : merge는 모든 필드를 덮어씌운다. 만약 필드에 값이 없다면 null로 업데이트 하는 위험이 있을 수 있다.

모든 필드를 덮어씌운다는 점이 merge와 Dirty Checking 간의 차이점이다.

profile
티스토리로 블로그 이전합니다. 최신 글들은 suhsein.tistory.com 에서 확인 가능합니다.

0개의 댓글