지독한 SQL의 굴레에서 저를 구원해준 것이 바로 ORM Framework 인 JPA와 Hibernate인데요.
정작 JPA와 Hibernate, Spring Data JPA의 관계에 대해 정립할 생각은 해보지 못했더군요. 이번 기회에 차이점을 정확하게 구분하는 예배시간을 갖고자 합니다.
참고로 ORM Framework
가 무엇인지는 아는 상태임을 가정하고 설명을 하겠습니다.
ORM Framework
역할은 Java 객체의 패러다임을 RDB 패러다임과 일치시키는 역할을 합니다.Hibernate
는 ORM Framework
중 하나입니다. Hibernate를 사용하는 프로세스를 간략하게나마 설명해드리자면 아래와 같은데요.
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.Session;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
Configuration config = new Configuration().configure("hibernate.cfg.xml");
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder()
.applySettings(config.getProperties());
sessionFactory = config.buildSessionFactory(builder.build());
} catch (Throwable ex) {
log.info("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
public class Main {
public static void main(String[] args) {
Session session = HibernateUtil.getSessionFactory().openSession();
// pseudo : 여기서부터 Hibernate을 사용하여 데이터베이스 작업을 수행
session.close();
}
}
익숙한 EntityManager
는 온데간데 없고 SessionFactory
를 사용하는 것이 눈에 띕니다. 당연히 EntityManager
는 없습니다! 그건 Hibernate
가 가지고 있던게 아니기 때문입니다.
Hibernate
는 EJB
의 모듈 중 하나로 개발이 시작되었습니다. 실제로 Hibernate 3.2을 보시면 EJB가 존재하고 있다는 것을 볼 수 있습니다.
시간이 흘러 Hibernate
의 가능성을 눈여겨 보고 EJB에서 분리해 별도의 모듈로 확장하게 되었고, 더욱 확장성 있는 ORM Framework를 제공하기 위해 java 5버전에서 부터 ORM에 관한 표준 API를 만들어 제공하게 되었습니다. (DB 벤더사에게 Connection 관련 SPI를 제공하는 JDBC
처럼요.) 이것이 바로 JPA(Java Persistence API) 입니다.
JPA에서는 PersistenceProvider
라는 spi를 제공하고 있습니다. ORM Framework들이 이를 구현하면 사용자측에서는 의존성을 추가하는 것 만으로도 별 다른 코드 없이 사용이 가능하죠!
package jakarta.persistence.spi;
...
/**
* Interface implemented by the persistence provider.
*
* <p> It is invoked by the container in Jakarta EE environments and
* by the {@link Persistence} class in Java SE environments to
* create an {@link EntityManagerFactory} and/or to cause
* schema generation to occur.
*
* @since 1.0
*/
public interface PersistenceProvider {
...
}
META-INF/services/jakarta.persistence.spi.PersistenceProvider`
org.hibernate.jpa.HibernatePersistenceProvider // hibernate
org.eclipse.persistence.jpa.PersistenceProvider // eclipselink
org.apache.openjpa.persistence.PersistenceProviderImpl // openjpa
구현체의 종류로는 hibernate
와 EclipseLink
, OpenJPA
가 있습니다.
결론적으로 JPA와 Hibernate는 다음와 같이 도식화할 수 있습니다.
Spring을 사용하시는 분들 중에서는 EntityManager
을 처음 보시는 분도 계실겁니다. 괜찮습니다. 그게 바로 Spring이 원한 방향이기 때문입니다.
public void persistSomething() {
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
// 작업 시작
transaction.commit();
}
문제는 DB에 접근할 때마다 EntityManager
와 Transaction
을 생성하는 코드를 작성해야 한다는 점입니다. 이런 BoilerPlate Code
를 줄일 방법이 없을까요?
Spring 진영에서는 JPA를 한 단계 더 추상화 한 Spring Data JPA 모듈을 제공함으로써 해당 BoilerPlate를 제거해주었습니다.
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
...
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager entityManager;
private final PersistenceProvider provider;
...
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
// Optional Wrapping도 담당함.
@Override
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(entityManager.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getHints();
return Optional.ofNullable(type == null ? entityManager.find(domainType, id, hints) : entityManager.find(domainType, id, type, hints));
}
...
}
덕분에 실제로 Spring Data Jpa를 사용하는 개발자 입장에서는 EntityManager
와 Transaction
은 생각하지 하지 않고 제공하는 API를 사용하면 됩니다.
public void saveEntity(AEntity aEntity) {
aRepository.save(aEntity);
}
결론적으로 다음과 같은 형태가 될 것 같네요
ORM Framework
의 표준 API Spec