본문 바로가기
Back-end

JPA

by 신재권 2023. 9. 20.

JPA란?

Java Persistence API의 약어로, 자바 ORM 기술에 대한 API 표준 명세이다.

쉽게 이야기해서 인터페이스를 모아둔 것이다.

JPA를 사용하려면 JPA를 구현한 ORM 프레임워크를 선택해야 하는데, 기본값으로 하이버네이트 프레임워크를 사용한다.

JPA를 쓰는 이유

JPA를 사용하면 객체지향 프로그래밍과 관계형 데이터베이스 간의 패러다임 불일치를 해결 할 수 있다.

또한, JPA를 통해 데이터베이스와의 상호작용을 편리하게 만들 수 있다.

개발자는 SQL 쿼리를 직접 작성하는 번거로움을 줄이고, POJO 기반 모델을 적용하여 객체 지향적인 코드를 작성할 수 있다.

JPA 단점

복잡한 SQL은 생성하지 못할 때도 있고, 직접 SQL을 호출하는 것보다 성능이 낮을 수 도 있다.

엔티티란?

엔티티는 경량 지속성 도메인 개체이다.

일반적으로 엔티티는 관계형 데이터베이스의 테이블을 나타내며 각 엔티티의 레퍼런스는 해당 테이블의 필드에 해당한다.

엔티티의 영속 상태는 영속 필드 또는 영속 속성을 통해 표현된다.

이러한 필드 또는 속성은 개체/관계형 매핑 어노테이션을 사용해 테이블에 매핑된다.

  • 영속 필드 : 엔티티 클래스가 영속 필드를 사용하는 경우 클래스 인스턴스 변수에 직접 액세스 한다. @Transient가 있는 필드를 제외한 모든 필드는 데이터 저장소에 유지된다. 즉, 객체/관계형 매핑 어노테이션들은 인스턴스 변수에 적용되어야 한다.
  • 영속 속성 : 엔티티가 영속 속성을 사용하는 경우 엔티티는 JavaBeans 구성 요소의 메서드 규칙을 따라야 한다. JavaBeans 스타일 속성은 일반적으로 엔티티 클래스

엔티티 매니저, 엔티티 팩토리

EntityManagerFactory는 데이터베이스와의 연결 및 EntityManager 생성을 담당한다.

EntityManagerFactory는 생성 비용이 비싸기 떄문에 대부분 하나의 데이터베이스에 하나만 생성하게 되고, 여러 스레드가 동시에 접근해도 안전하다.

EnttiyManager는 JPA에서 엔티티 객체를 관린하고 영속성 컨텍스트를 관리하는 핵심 컴포넌트이다.

EntityManager는 데이터베이스와의 트랜잭션을 관리하고 CRUD 작업을 수행한다.

기본적으로 애플리케이션은 EntityManagerFactory와 DB Connection Pool은 한개씩 생성된다.

EntityManager은 연결이 꼭 필요한 시점 까지 커넥션을 얻지 않는다.

일반적으로 1개의 EntityManager에 1개의 Persistence Context 가 생성된다.

EntityManager가 사용이 완료되면 Persistence Context가 DB에 flush 된다.

엔티티 매니저를 단독으로 쓰면 스레드 세이프 하지 않다.

스프링은 어떻게 세이프하게 해결하였는가?

엔티티 매니저는 스레드 세이프 하지 않아, 스프링은 이를 해결하기 위해 스레드 로컬 패턴을 사용한다.

각 스레드에 대한 별도의 엔티티 매니저를 제공하고, 스레드가 종료될 때 엔티티 매니저를 자동으로 닫아 누수를 방지한다.

이를 통해 엔티티 매니저를 스레드 세이프하게 사용할 수 있다.

엔티티 매니저 홀더에 엔티티 매니저를 저장하고, 각 스레드에서 스레드 로컬 변수를 통해 자신의 엔티티 매니저를 검색하고 사용한다.

영속성 컨텍스트란?

영속성 컨텍스트는 JPA에서 엔티티 객체를 관리하는 논리적인 컨테이너이다.

영속성 컨텍스트는 엔티티 매니저를 통해 생성되며, 엔티티 객체의 생명주기를 관리하고 데이터베이스와의 동기화를 담당한다.

  • 1차 캐시 : 영속성 컨텍스트 내에서 엔티티 객체를 캐싱하여 중복 조회를 피하고 성능을 향상 시킨다.
  • 더티 체킹 : 엔티티의 상태 변화를 감지하여 변경된 데이터를 데이터베이스에 동기화 한다. 엔티티를 최초에 영속성 컨텍스트에 보관하면, 최초 상태를 저장하는데, 이를 스냅샷 이라한다. JPA는 플러시 시점에 스냅샷과 엔티티를 비교해 변경된 것을 찾고, 수정 쿼리를 생성해 SQL 저장소에 보낸다.
  • 트랜잭션 범위 내에서 데이터베이스 트랜잭션을 관리한다.
  • 동일성을 보장 : 같은 것을 두번 조회하면, 결국 마지막 조회때는 1차 캐시의 값을 그대로 가져오므로, 동일성을 보장한다.
  • 쓰기 지연 : 커밋하기 직전까지 내부 쿼리 저장소에 SQL을 쌓아둔다. 데이터베이스에 저장이 안될 뿐 1차 캐시에 저장된다. 모아둔 SQL을 한번에 전달해 성능을 최적화 한다.
  • 지연 로딩 : 필요한 시점에 SQL을 날린다.

2차 캐시

JPA 2차 캐시는 영속성 컨텍스트 밖에서 엔티티를 캐싱하는 메커니즘이다.

이를 통해 데이터베이스 조회를 줄이고 성능을 향상시킨다. 주로 분산 환경에서의 성능 최적화에 활용된다.

영속성 컨텍스트는 추상화된 개념이다.

실제로는 어디에 존재할까?

영속성 컨텍스트는 추상화된 개념이다.

애플리케이션 실행 중에는 JVM 메모리에 존재한다.

즉, 애플리케이션 실행 중 동적으로 생성되기 때문에 힙 영역에 존재한다.

트랜잭션 특징(ACID)

  • 원자성(Atomicity) : 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것 처럼 모두 성공하든가, 모두 실패해야 한다.
  • 일관성(Consistency) : 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다. 데이터베이스에서 정한 무결성 제약 조건을 항상 만족해야 한다.
  • 격리성(Isolcation) : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다.
  • 지속성(Durability) : 트랜잭션을 성공적으로 끝내면 그 결과는 항상 기록되어야 한다. 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용하여 성공한 트랜잭션 내용을 복구해야 한다.

트랜잭션 간 격리성을 완벽하게 보장하려면 트랜잭션을 거의 차례대로 실행해야 한다. 이렇게 처리하면 동시성 처리 성능이 매우 나빠진다. 이런 문제로 트랜잭션 격리 수준을 4단계로 나누어 정의하였다.

DB 트랜잭션 격리 수준(Isolation Level)

  • READ UNCOMMITED : Dirty Read, Non-Repeatable Read, Phantom Read
  • READ COMMITED : Non-Repeatable Read, Phantom Read
  • REPEATABLE READ : Phantom Read
  • SERIALIZABLE

READ UNCOMMITED의 격리수준이 가장 낮고, SERIALIZABLE의 격리 수준이 가장 높다.

격리 수준이 낮을 수록 동시성은 증가하지만, 격리 수준에 따른 다양한 문제가 발생한다.

READ UNCOMMITED

커밋하지 않은 데이터를 읽을 수 있다.

트랜잭션 1이 데이터를 수정하고 있는데, 커밋하지 않아도 트랜잭션 2가 수정중인 데이터를 조회할 수 있다. 이를 Dirty Read 라고 한다.

트랜잭션 2가 Dirty Read 한 데이터를 사용하는데, 트랜잭션 1이 롤백되면, 데이터 정합성에 문제가 발생한다.

READ COMMITED

커밋한 데이터만 읽을 수 있다.

따라서 Dirty Read가 발생하지 않는다.

Non-Repeatable Read은 발생한다.

트랜잭션 1이 회원 A를 조회 중인데, 트랜잭션 2가 회원 A를 수정하고 커밋하면 트랜잭션 1이 다시 회원 A를 조회했을 때 수정된 데이터가 조회된다.

이처럼 반복해서 같은 데이터를 읽을 수 없는 상태를 Non-Repeatable Read 라고 한다.

REPEATABLE READ

한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회된다.

하지만 Phantom Read는 발생할 수 있다.

트랜잭션이 10살 이하의 회원을 조회했는데, 트랜잭션 2가 5살 회원을 추가하고 커밋하면, 트랜잭션1이 다시 10살 이하의 회원을 조회했을 때 회원 하나가 추가된 상태로 조회된다.

이처럼 반복 조회 시 결과 집합이 달라지는 것을 PHANTOM READ라고 한다.

SERIALIZABLE

가장 엄격한 트랜잭션 격리 수준이다.

동시성 처리 성능이 떨어진다.

영속성 컨텍스트 1차 캐시와 트랜잭션 레벨의 관계

엔티티를 DB에서 조회하면 영속화가 되고, 1차 캐시에 저장이된다.

여기서 반복적으로 같은 엔티티를 조회하면, 결국 1차 캐시에 있는 엔티티가 반환이 된다.

결국, 1차 캐시로 인해 REPEATABLE READ 격리 수준을 애플리케이션 수준에서 제공해준다.

똑같이 팬텀 리드 문제는 발생한다.

캐시 전략

JPA 1차 캐시는 Read-Through 전략을 사용한다.

Read-Through 캐시는 DB와 하나의 라인(프로세스)에 묶인다.

Cash Miss가 발생했을 때 DB로 부터, 데이터를 가져와 캐시에 저장한 후 애플리케이션에 반환된다.

더티체킹 원리

스냅샷이 찍힐 때는 영속성 컨텍스트에 저장될 때 찍힌다.

영속성 컨텍스트에 저장한다는 행위는 엔티티를 저장하거나, 엔티티를 조회할 때 밖에 없다.

즉, 해당 행위를 하면 스냅샷이 찍히게 되는데, 스냅샷과 커밋이전의 엔티티 상태를 확인해 변경점이 있다면 내부 SQL 쓰기 지연 저장소에 Update 쿼리를 작성하게 되어, 더티체킹이 일어난다.

'Back-end' 카테고리의 다른 글

기본 키 할당 전략  (0) 2023.09.29
Wrapper Type vs Primitive Type In JPA  (0) 2023.09.29
Spring MVC  (0) 2023.09.15
@Bean, @Configuration  (0) 2023.09.14
불변성  (0) 2023.09.08