ORM

Dec 13, 2024 · 1일 전

Entity

엔티티는 데이터베이스의 테이블과 매핑되는 객체를 의미한다. 본질적으로 엔티티는 자바 객체이다. 하지만 데이터베이스의 테이블과 직접 연결된다는 아주 특별한 특징이 있어 구분지어 부른다.

Entity Manager

엔티티 매니저는 엔티티를 관리하여 데이터베이스와 애플리케이션 사이에서 객체를 생성, 수정, 삭제하는 등의 역할을 한다. 그리고 이런 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리 이다.

예를 들어 회원 2명이 동시에 회원 가입을 하려는 경우 엔티티 매니저 팩토리는 회원1을 가입시킬 엔티티 매니저1과 회원2를 가입시킬 엔티티 매니저2를 만들고 각 엔티티 매니저는 필요한 시점에 데이터베이스와 연결한 뒤에 쿼리한다.

그렇다면 스프링 부트에서도 직접 엔티티 매니저 팩토리를 만들어 관리할까? 그렇지 않다. 스프링 부트는 내부에서 엔티티 매니저 팩토리를 하나만 생성해서 관리하고 @PersistenceContext 또는 @Autowired 애너테이션을 사용해서 엔티티 매니저를 사용한다.

// 스프링부트가_엔티티매니저를_사용하는_방법.java
@PersistenceContext
EntityManager em; // 프록시 엔티티 매니저, 필요할 때 진짜 엔티티 매니저 호출

그리고 스프링 부트는 기본적으로 빈을 하나만 생성해 공유하므로 동시성 문제가 발생할 수 있다. 하여 실제로는 엔티티 매니저가 아닌 실제 엔티티 매니저와 연결하는 프록시(가짜) 엔티티 매니저를 사용한다. 필요할 때만 데이터베이스 트랜잭션과 관련된 실제 엔티티 매니저를 호출하게 된다.

영속성 컨텍스트란?

엔티티 매니저는 엔티티를 영속성 컨텍스트에 저장한다는 특징이 있다. 이 영속성 컨텍스트는 JPA의 중요한 특징 중 하나로, 엔티티를 관리하는 가상의 공간이다. 영속성 컨텍스트에는 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩이라는 특징이 있다.

  • 1차 캐시 영속성 컨텍스트는 내부적으로 1차 캐시를 가지고 있다. 이때 이 캐시의 키는 엔티티의 @Id 애너테이션이 달린 기본키 역할을 하는 식별자이며 값은 엔티티이다. 엔티티를 조회하면 1차 캐시에서 우선적으로 데이터를 조회하고 값이 있으면 값을 반환한다. 값이 없으면 데이터베이스에서 조회해 1차 캐시에 저장한 뒤 반환한다. 캐시된 데이터는 빠르게 접근할 수 있다.

  • 쓰기 지연 쓰기 지연 transactional write-behind 은 트랜잭션을 커밋하기 전까지는 쿼리를 모았다가 트랜잭션을 커밋하면 모았던 쿼리를 한번에 실행하는 것을 의미한다. 이를 통해 적당한 묶음으로 쿼리를 요청할 수 있어 데이터베이스 시스템의 부담을 줄일 수 있다.

  • 변경 감지 트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 엔티티의 값과 대조하여 변경된 값이 있다면 변경 사항을 감지해 변경된 값을 데이터베이스에 자동으로 반영한다. 이를 통해 쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있고, 데이터베이스 시스템의 부담을 줄일 수 있다.

  • 지연 로딩 지연 로딩 lazy loading 은 쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터를 조회하는 것을 의미한다.

이러한 특징들이 갖는 공통점은 데이터베이스의 접근을 최소화해 성능을 높이는 것이다. 캐시를 통해 요청을 줄이거나 변화를 감지해서 미리 준비하거나 하는 등의 방법으로 말이다.

엔티티의 상태

엔티티는 4가지 상태를 가진다. 영속성 컨텍스트가 관리하고 있지 않는 분리 detached 상태, 영속성 컨텍스트가 관리하는 관리 managed 상태, 영속성 컨텍스트와 전혀 관계가 없는 비영속 transient 상태, 삭제된 removed 상태로 나뉜다. 이 상태들은 특정 메서드로 변경이 가능한데, 필요에 따라 엔티티의 상태를 조절해 데이터를 올바르게 유지하고 관리할 수 있다.

public class EntityManagerTest {
@Autowired
EntityManager em;
public void example() {
// 엔티티 매니저가 엔티티를 관리하지 않는 상태(비영속 상태)
Member member = new Member(1L, "홍길동");
// 엔티티가 관리되는 상태
em.persist(member);
// 엔티티 객체가 분리된 상태
em.detach(member);
// 엔티티 객체가 삭제된 상태
em.remove(member);