JPA 영속성을 공부하면서 EntityManager를 통해 entityManager를 통해 entity CRUD를 했는데, 프로젝트를 진행하면서는 EntityManager이라는 코드는 1도 보이지 않아서 개념을 이해 하는데 혼란을 가지게 되었다.
이 블로그 를 통해 개념을 바로 잡을 수 있어서 관련 내용을 정리하고자 한다.
JPA는 Java Persistence API의 약자.
자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스.
JPA는 말 그대로 인터페이스이다. 특정 기능을 하는 라이브러리가 아니기 때문에 JPA를 사용하기 위해서 구현체가 필요하다. 대표적으로 Hibernate, Eclipse Link 등이 있다.
ex) 일반적인 백엔드 API가 클라이언트가 어떻게 서버를 사용해야 하는지를 정의한 것처럼, JPA 역시 자바 어플리케이션에서 관계형 데이터베이스를 어떻게 사용해야 하는지를 정의하는 한 방법일 뿐이다.
JPA를 정의한 javax.persistence 패키지의 대부분은 inteface, enum, Exception, 그리고 각종 Annotation으로 이루어져 있다.
예를 들어, JPA의 핵심이 되는 EntityManager는 아래와 같이 javax.persistence.EntityManager 이라는 파일에 interface로 정의 되어있다.
package javax.persistence;
import ...
public interface EntityManager {
public void persist(Object entity);
public <T> T merge(T entity);
public void remove(Object entity);
public <T> T find(Class<T> entityClass, Object primaryKey);
// More interface methods...
}
위에서 말한 것처럼, Hibernate는 JPA라는 명세의 구현체이다.
javax.persistence.EntityManager와 같은 인터페이스를 직접 구현한 라이브러리이다. JPA와 Hibernate는 자바에서 interface와 이를 구현한 class와 같은 관계로 보면 된다.
위 사진을 보면 JPA의 핵심인 EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서는 각각 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있다.
JPA를 사용하기 위해서 반드시 Hibernate를 사용할 필요는 없고, DataNucleus, EclipseLinke 등 다른 JPA 구현체를 사용해도 된다.
Spring에서는 JPA를 사용할 때는 이 구현체들을 직접 다루지 않는다.
구현체들을 좀 더 쉽게 사용하고자 추상화시킨 Spring Data JPA라는 모듈을 이용한다.
따라서, 위와 같은 관계로 정의할 수 있다.
Spring으로 개발하면서 단 한 번도 EntityManager를 직접 다뤄본 적 이 없다. DB에 접근할 필요가 있는 대부분의 상황에서는 Repository를 정의하여 사용했다. 이 Repository가 바로 Spring Data JPA의 핵심이다.
Spring Data JPA는 Spring에서 제공하는 모듈 중 하나로, 개발자가 JPA를 더 쉽고 편하게 사용할 수 있도록 도와준다.
이는 JPA를 한 단계 추상화시킨 Reposiroy라는 인터페이스를 제공함으로써 이루어진다. 사용자가 Repository 인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.
Spring Data JPA가 JPA를 추상화했다는 말은, Spring Data JPA의 Repository의 구현에서 JPA를 사용하고 있다는 것이다. 예를 들어, Repository 인터페이스의 기본 구현체인 SimpleJpaRepository의 코드를 보면 아래와 같이 내부적으로 EntityManager을 사용하고 있는 것을 볼 수 있다.
package org.springframework.data.jpa.repository.support;
import ...
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
private final EntityManager em;
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}
// Other methods...
}