[JPA] 엔티티와 영속성 컨텍스트

Sungmin kim·2023년 3월 11일
0
post-thumbnail

안녕하세요. 백엔드 개발자를 목표로 학습하는 주니어개발자입니다. 오늘은 JPA의 엔티티와 영속성컨텍스트를 학습한 내용을 간략하게 정리해보았습니다. 코드 예제보다는 이론 위주로 정리한 내용이라 JPA에 대한 학습이 없을 시 어려울 수 있다고 생각합니다.

엔티티

엔티티는 DB 테이블과 매핑하기 위한 객체입니다.

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String userName;

    @Embedded
    private Address homeAddress;
		
		...
}

위 예제는 엔티티를 테이블과 매핑하기 위해 어노테이션을 사용한 상태입니다.

이러한 엔티티를 관리하는 관리자 역할을 엔티티 매니저가 수행합니다.
여기서 뜻하는 '관리'는 엔티트를 저장/조회/수정/삭제하는 일을 뜻합니다.
그럼 엔티티 매니저는 어떻게 만들어질까요?


엔티티 매니저가 생성되는 절차는 아래와 같습니다.

  1. Persistence.*createEntityManagerFactory(String persistenceUnitName)* 를 호출합니다. (이때 Persistence 는 javax.persistence에 속해 있는 객체입니다.)
  2. 개발자가 설정한 persistence.xml 정보를 바탕으로 EntityManagerFactroy를 생성합니다.
  3. 생성된 EntityManagerFactroy를 통해 EntityManager를 만들어 냅니다.

자, 그렇다면 왜 JPA는 엔티티를 관리하기 위해서 이런 번거로운 절차를 거칠까요?

우선 엔티티를 관리하기 위해서는 DB와 연관된 필수 속성과 개발시 도움을 주는 개별 옵션을 바탕으로 생성된 EntityManagerFactroy가 필요합니다. 하지만 EntityManagerFactroy를 생성하기 위해서는 아주 많은 비용이 들게됩니다.

“비용이 많이 든다” 어떤 의미 일까요?
저는 특정 객체를 만들어내는 과정에서 시스템 자원이 비교적 많이 사용된다는 의미로 받아들이고 있습니다.

이러한 사항을 보완하기 웹 애플리케이션에서 EntityManagerFactroy를 한 개만 만들어 애플리케이션 전체에 공유하도록 설계되었습니다. 이러한 설계 방식은 곧 여러 스레드가 동시에 접근해도 안전하다는 의미가 됩니다. 이렇게 만들어진 EntityManagerFactroy를 통해 특정 비즈니스 요청에 맞는 각각의 EntityManager를 생성하게 됩니다.

지금까지 엔티티는 무엇인지 그리고 무엇에 의해 관리되는지를 알아보았습니다.

그런데 여기서 의문이 듭니다. '그럼 엔티티 매니저가 엔티티를 분석하여 데이터를 DB에 바로 넣어주는건가?'
물론 DB에 바로 데이터를 적용시킬 수도 있을 것 같습니다. 하지만 그닥 효율적이지 못하다는 생각이 듭니다.

이런 점을 효율화하기 위해 JPA는 트랜잭션을 이용했으며, 트랜잭션 안에서 Entity를 관리하기 위한 개념적 공간을 JPA에서는 '영속성 컨텍스트(persistence context)'라고 부릅니다.


영속성 컨텍스트(persistence context)

우리말로 번역시 '엔티티를 영구 저장하는 환경'이라고 합니다. 저는 조금 더 쉽게 이해하기 위해 '엔티티 매니저가 관리하는 엔티티를 저장하는 공간' 이라고 정의하고 있습니다.

그렇다면 엔티티 매니저와 영속성 컨텍스트를 통해 엔티티는 어떻게 관리될까요?

엔티티에는 4가지 상태가 존재하며, 이 4가지 상태를 기반으로 위 그림과 같이 생명주기를 나타낼 수 있습니다.

비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
영속(Managed) : 영속성 컨텍스트에 저장된 상태
준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제(removed) : 삭제된 상태

이러한 생명주기를 통해 엔티티는 관리되며, 영속성 컨텍스트가 관리를 위한 공간을 나타낸다고 볼 수 있습니다.

사실 공간은 다른 것을 사용해도 되지 않을까 하는 생각도 듭니다. 그럼에도 불구하고 영속성 컨텍스트 사용한 이유는 얻을 수 있는 장점이 있기 때문이 아닐까요?

"자바 ORM 표준 JPA 프로그래밍" 도서에서는 영속성 컨텍스트로 엔티티를 관리할 경우의 장점을 아래와 같이 5가지로 정리했습니다.

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지(dirty checking)
  • 지연 로딩

영속성 컨텍스트의 장점

1. 1차 캐시

1차 캐시는 영속성 컨텍스트 내부에 위치하여 영속 상태의 엔티티를 저장하는 공간으로 Map 형태의 key는 엔티티명, value는 엔티티 인스턴스를 저장하게 됩니다.

JPA에서 엔티티 매니저를 사용하여 엔티티를 조회할때 DB를 바로 조회하지 않고 메모리에 있는 1차 캐시에서 우선 조회합니다. 만약 1차 캐시에 엔티티가 존재하지 않을 경우 DB를 조회해서 엔티티를 생성 후 1차 캐시를 저장합니다. 이러한 특징으로 인해 경우에 따라 DB를 조회하는 작업이 적어지며, 이는 곧 성능상 이점을 얻는 것을 의미합니다.

2. 동일성 보장

동일한 엔티티를 조회했을 때 영속성 컨텍스트는 1차 캐시에 있는 같은 인스턴스를 반환 합니다. 이러한 특징으로 반복 호출에도 같은 인스턴스로 조회가 되며 '==' 비교가 가능해집니다. 이는 성능상 이점으로 이어지게 됩니다.

3. 트랜잭션을 지원하는 쓰기 지연

트랜잭션은 커밋하기전까지 DB에 SQL을 전달하지 않습니다. 이러한 특징을 이용하여 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 쿼리를 모아두며, 커밋되는 시점에 모아둔 쿼리를 모두 DB에 보내는데, 이러한 기능을 transactional write-behind(트랜잭션을 지원하는 쓰기지연)라고 합니다. 쿼리를 한번에 모아서 전달하기때문에 활용 방법에 따라 성능을 최적화할 수 있습니다.

4. 변경 감지(dirty checking)

트랜잭션을 커밋시 엔티티 매니저는 내부의 flush()가 우선 호출되며, 엔티티와 스냅샷을 비교해서 변경된 사항을 찾습니다. 이때 수정된 사항이 있을 경우 수정에 대한 쿼리를 쓰기 지연 SQL 저장소에 전달하며, 최종적으로 DB 트랜잭션에 커밋하게 됩니다. 이는 준영속 상태의 엔티티를 수정할 때 이점을 가지게됩니다.
(해당 내용을 간단히 정리하기 어려운 내용임으로 차후 별도의 게시글로 정리해보겠습니다.)

flush()란?
영속성 컨텍스트의 변경 내용을 DB에 반영하는 역할을 합니다. 이는 영속성 컨텍스트를 비우는 것이 아닌 변경된 내용을 DB에 동기화 시키는 것을 의미합니다.

스냅샷이란?
엔티티를 영속성 컨텍스트에 보관할 때 엔티티의 최초의 상태를 복사하여 저장한 것을 의미합니다.

5. 지연 로딩

지연 로딩이란 필요한 시점에 연관된 객체의 데이터를 불러오는 것을 의미합니다.
예를 들어 특정 엔티티를 조회했을 때 연관관계인 엔티티의 데이터가 필요하지 않음에도 연관관계 설정을 즉시 로딩으로 설정할 경우 필요하지 않은 데이터에 대한 쿼리도 전달하게 됩니다. 이러한 점을 효율화하기 위해 JPA는 연관관계인 엔티티의 데이터를 조작하는 메서드를 호출할때 쿼리가 전달되도록 지연 로딩이라는 옵션을 제공하고 있습니다.


참고자료

자바 ORM 표준 JPA 프로그래밍 - 저자 : 김영한

profile
Keep Trying & Enjoing

0개의 댓글