JPA는 표준 API이지만 실제 구현은 Hibernate·EclipseLink 같은 벤더가 한다. 각 벤더는 (1) provider 클래스, (2) 고유 프로퍼티 키, (3) 표준에 없는 기능(JDBC Connection 접근, 격리수준, flush mode)을 제각각 갖는다. JpaVendorAdapter는 이 벤더별 차이를 한 곳에 모아, 사용자가 setDatabase(H2)·setShowSql(true) 같은 추상 설정만 하면 벤더 프로퍼티로 번역해 준다.
또한 Hibernate는 JPA를 거치지 않는 네이티브 경로(SessionFactory/Session)도 제공한다. orm.jpa.hibernate 패키지는 이 네이티브 경로를 Spring 트랜잭션과 통합한다. 이 챕터는 두 가지 — 벤더 어댑터(vendor)와 네이티브 Hibernate(hibernate) — 를 다룬다.
┌──────────────────────────────────────────────┐
│ JpaVendorAdapter (interface) │
│ getPersistenceProvider() │
│ getJpaPropertyMap() / getJpaPropertyMap(pui) │
│ getJpaDialect() │
│ getEntityManager(Factory)Interface() │
│ postProcessEntityManager(Factory)() │
└──────────────┬───────────────────────────────┘
│ implements
┌──────────────▼──────────────────┐
│ AbstractJpaVendorAdapter │ 공통 설정:
│ database, databasePlatform, │ Database enum, showSql,
│ generateDdl, showSql │ generateDdl
└──────────────┬──────────────────┘
┌────────┴─────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ HibernateJpa │ │ EclipseLinkJpa │
│ VendorAdapter│ │ VendorAdapter │
│ + Hibernate │ │ │
│ JpaDialect │ │ │
│ + SpringBean│ │ │
│ Container │ │ │
└──────────────┘ └──────────────────┘
AbstractJpaVendorAdapter(소스 38행)는 Database(DB2/H2/MYSQL/ORACLE/POSTGRESQL...), showSql, generateDdl 같은 벤더 중립 설정만 보유한다. 하위 클래스가 이를 벤더 프로퍼티로 번역한다. getJpaDialect() 등은 기본 빈/null을 반환하고 하위에서 오버라이드한다.
HibernateJpaVendorAdapter(소스 76행)는 생성 시 SpringHibernateJpaPersistenceProvider와 HibernateJpaDialect를 만들고, BeanFactoryAware를 통해 Hibernate BeanContainer 통합과 JTA 플랫폼 설정까지 자동 구성한다. 02장에서 본 AbstractEntityManagerFactoryBean.afterPropertiesSet()이 이 어댑터에서 provider·dialect·프로퍼티를 끌어다 쓴다.
04장에서 DefaultJpaDialect가 격리수준·JDBC 노출을 못 한다고 했다. HibernateJpaDialect(소스 60행)가 이를 채운다.
HibernateJpaDialect.beginTransaction(em, definition)
│
├─ Session으로 unwrap
├─ read-only면 flush mode를 MANUAL/EXPLICIT로
├─ prepareConnection=true 이고 격리수준/read-only 지정됐으면
│ → 내부 JDBC Connection에 직접 setTransactionIsolation / setReadOnly
│ (DataSourceUtils.prepareConnectionForTransaction 활용)
├─ 타임아웃 → Hibernate Transaction.setTimeout
├─ session.getTransaction().begin()
└─ SessionTransactionData(이전 flush mode/격리수준 등) 반환
→ 나중에 cleanupTransaction에서 원복
HibernateJpaDialect.getJdbcConnection(em, readOnly)
→ Session에서 실제 JDBC Connection 핸들 추출 (HibernateConnectionHandle)
→ JpaTransactionManager가 이걸로 DataSource에 Connection 바인딩
setPrepareConnection(true)(기본값, 소스 68행)가 켜져 있어야 per-transaction 격리수준과 read-only 전파가 동작한다. 이 플래그를 끄면 Connection 설정을 건드리지 않아 트랜잭션 후 정리가 불필요해진다. 예외 변환은 내부 HibernateExceptionTranslator에 위임한다.
┌─────────────────────────────────────────────────────────┐
│ LocalSessionFactoryBean (FactoryBean<SessionFactory>) │
│ - Hibernate Configuration/MetadataSources로 SF 빌드 │
│ - DataSource, packagesToScan, 네이밍전략, 인터셉터... │
│ - 7.0: SF가 JPA EntityManagerFactory도 네이티브 노출 │
│ - SpringBeanContainer 자동 등록 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────┐ ┌──────────────────────────┐
│ HibernateTransactionManager │ │ SpringSessionContext │
│ SessionFactory용 PTM │ │ Hibernate CurrentSession-│
│ (JpaTransactionManager와 │ │ Context 구현 │
│ 유사하나 Session 기반) │ │ sessionFactory │
│ JPA+네이티브 Hibernate 혼용 │ │ .getCurrentSession() │
└─────────────────────────────┘ │ → 스프링 트랜잭션 Session │
└──────────────────────────┘
보조: SessionHolder, SpringSessionSynchronization,
SpringFlushSynchronization, SpringJtaSessionContext,
SharedSessionCreator, SpringBeanContainer,
ConfigurableJtaPlatform, HibernateExceptionTranslator
LocalSessionFactoryBean(소스 83행)은 Hibernate SessionFactory를 빈으로 노출한다. 7.0부터의 핵심은 주석(소스 65~72행)에 명시되어 있다: 이 SessionFactory는 JPA EntityManagerFactory 인터페이스도 네이티브로 노출하고, BeanContainer 통합이 기본 등록된다. 따라서 LocalContainerEntityManagerFactoryBean의 즉각적 대안이 되며, HibernateTransactionManager와 함께 쓰면 한 트랜잭션 안에서 JPA 코드와 네이티브 Hibernate 코드를 자유롭게 섞을 수 있다.
HibernateTransactionManager(소스 59행)는 JpaTransactionManager와 구조적으로 쌍둥이다. 둘 다 AbstractPlatformTransactionManager + ResourceTransactionManager를 구현하고 JdbcTransactionObjectSupport로 JDBC 혼용을 지원한다. 차이는 자원이 EMF/EntityManager가 아니라 SessionFactory/Session이라는 점, 그리고 Hibernate Transaction API를 직접 쓴다는 점이다.
네이티브 Hibernate 코드는 보통 sessionFactory.getCurrentSession()으로 Session을 얻는다. SpringSessionContext(소스 59행, CurrentSessionContext 구현)가 이 호출을 가로채 현재 스프링 트랜잭션에 묶인 Session을 반환한다.
sessionFactory.getCurrentSession()
→ Hibernate가 설정된 CurrentSessionContext(=SpringSessionContext)에 위임
→ SpringSessionContext.currentSession()
TransactionSynchronizationManager에서 SessionFactory로 SessionHolder 조회
있으면 그 Session 반환
JTA면 SpringJtaSessionContext로 위임
hibernate.current_session_context_class 프로퍼티에 이 클래스를 지정하면 동작하며, HibernateTransactionManager/LocalSessionFactoryBean 사용 시 자동 설정된다. 7.2+의 stateless session 파생도 감지한다(소스 61행).
SpringBeanContainer(소스 59행)는 Hibernate의 BeanContainer SPI를 구현해 Spring BeanFactory에 위임한다. 그래서 Hibernate가 인스턴스화하는 컴포넌트(커스텀 타입, AttributeConverter, 엔티티 리스너 등)가 Spring 의존성 주입을 받을 수 있다. LocalSessionFactoryBean.setBeanFactory()가 자동 등록하거나, JPA 프로퍼티 hibernate.resource.beans.container로 수동 설정한다(소스 44~63행 예시).
@Transactional (HibernateTransactionManager)
│
├─ doBegin: SessionFactory로 Session 열고 Transaction.begin
│ SessionHolder를 SessionFactory 키로 스레드 바인딩
│ (DataSource 설정 시 JDBC Connection도 노출)
│
├─ 비즈니스: JPA EntityManager (공유 프록시) 사용
│ → 같은 Session을 EntityManager로 노출 (SF가 EMF 겸함)
│ 동시에: sessionFactory.getCurrentSession()
│ → SpringSessionContext가 같은 Session 반환
│ 동시에: JdbcTemplate
│ → DataSourceUtils가 노출된 같은 Connection 사용
│
└─ doCommit: Transaction.commit (= flush)
세 종류의 데이터 접근(JPA EM, 네이티브 Session, 순수 JDBC)이 하나의 Session/Connection/트랜잭션을 공유한다. 이것이 orm.jpa.hibernate가 존재하는 이유다.
| 메서드 | 위치 | 하는 일 |
|---|---|---|
getJpaPropertyMap() |
HibernateJpaVendorAdapter |
추상 설정(database/showSql)을 Hibernate 프로퍼티로 번역 |
beginTransaction() |
HibernateJpaDialect |
격리수준/read-only를 JDBC Connection에 적용 + Session 트랜잭션 시작 |
getJdbcConnection() |
〃 | Session에서 JDBC Connection 핸들 추출 |
buildSessionFactory() |
LocalSessionFactoryBean |
Configuration → SessionFactory 빌드 |
currentSession() |
SpringSessionContext |
스레드 바인딩 Session 반환 |
getBean() |
SpringBeanContainer |
Hibernate 컴포넌트를 Spring 빈으로 해결 |
- 추상 설정 → 벤더 프로퍼티 번역:
Database.H2같은 enum 하나가 Hibernate dialect 클래스로 번역된다. 벤더 교체 시 어댑터만 바꾸면 된다. - 두 경로의 대칭성:
LocalContainerEntityManagerFactoryBean+JpaTransactionManager(JPA 경로)와LocalSessionFactoryBean+HibernateTransactionManager(네이티브 경로)가 거의 같은 구조다. 7.0에서 SessionFactory가 EMF를 겸하게 되어 두 경로의 경계가 흐려졌다. - SPI 어댑팅:
SpringSessionContext(HibernateCurrentSessionContext)와SpringBeanContainer(HibernateBeanContainer)는 둘 다 "Hibernate의 확장점을 Spring으로 연결"하는 어댑터다.
vendor 패키지는 벤더별 차이를 JpaVendorAdapter 한 점으로 모아 추상 설정을 벤더 프로퍼티로 번역하고, HibernateJpaDialect로 04장의 SPI를 채워 격리수준·JDBC 노출을 가능하게 한다. hibernate 패키지는 네이티브 Hibernate 경로(LocalSessionFactoryBean+HibernateTransactionManager)를 제공하며, SpringSessionContext·SpringBeanContainer가 Hibernate의 확장점을 Spring 트랜잭션·DI와 연결한다. 결과적으로 JPA·네이티브 Hibernate·순수 JDBC를 하나의 트랜잭션에서 혼용할 수 있다.