Skip to content

Latest commit

 

History

History
159 lines (124 loc) · 11.7 KB

File metadata and controls

159 lines (124 loc) · 11.7 KB

05. Vendor와 Hibernate 통합

무엇을 / 왜

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) — 를 다룬다.

핵심 타입 — vendor 패키지

 ┌──────────────────────────────────────────────┐
 │ 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행)는 생성 시 SpringHibernateJpaPersistenceProviderHibernateJpaDialect를 만들고, BeanFactoryAware를 통해 Hibernate BeanContainer 통합과 JTA 플랫폼 설정까지 자동 구성한다. 02장에서 본 AbstractEntityManagerFactoryBean.afterPropertiesSet()이 이 어댑터에서 provider·dialect·프로퍼티를 끌어다 쓴다.

HibernateJpaDialect — 04장 SPI의 Hibernate 구현

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에 위임한다.

핵심 타입 — hibernate 패키지 (네이티브 경로)

 ┌─────────────────────────────────────────────────────────┐
 │ 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 (7.0 신규)

LocalSessionFactoryBean(소스 83행)은 Hibernate SessionFactory를 빈으로 노출한다. 7.0부터의 핵심은 주석(소스 65~72행)에 명시되어 있다: 이 SessionFactory는 JPA EntityManagerFactory 인터페이스도 네이티브로 노출하고, BeanContainer 통합이 기본 등록된다. 따라서 LocalContainerEntityManagerFactoryBean의 즉각적 대안이 되며, HibernateTransactionManager와 함께 쓰면 한 트랜잭션 안에서 JPA 코드와 네이티브 Hibernate 코드를 자유롭게 섞을 수 있다.

HibernateTransactionManager

HibernateTransactionManager(소스 59행)는 JpaTransactionManager와 구조적으로 쌍둥이다. 둘 다 AbstractPlatformTransactionManager + ResourceTransactionManager를 구현하고 JdbcTransactionObjectSupport로 JDBC 혼용을 지원한다. 차이는 자원이 EMF/EntityManager가 아니라 SessionFactory/Session이라는 점, 그리고 Hibernate Transaction API를 직접 쓴다는 점이다.

SpringSessionContext — getCurrentSession()의 비밀

네이티브 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 — Hibernate가 Spring 빈을 쓰게

SpringBeanContainer(소스 59행)는 Hibernate의 BeanContainer SPI를 구현해 Spring BeanFactory에 위임한다. 그래서 Hibernate가 인스턴스화하는 컴포넌트(커스텀 타입, AttributeConverter, 엔티티 리스너 등)가 Spring 의존성 주입을 받을 수 있다. LocalSessionFactoryBean.setBeanFactory()가 자동 등록하거나, JPA 프로퍼티 hibernate.resource.beans.container로 수동 설정한다(소스 44~63행 예시).

동작 흐름 — 한 트랜잭션 안 JPA + 네이티브 Hibernate 혼용

 @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(Hibernate CurrentSessionContext)와 SpringBeanContainer(Hibernate BeanContainer)는 둘 다 "Hibernate의 확장점을 Spring으로 연결"하는 어댑터다.

정리

vendor 패키지는 벤더별 차이를 JpaVendorAdapter 한 점으로 모아 추상 설정을 벤더 프로퍼티로 번역하고, HibernateJpaDialect로 04장의 SPI를 채워 격리수준·JDBC 노출을 가능하게 한다. hibernate 패키지는 네이티브 Hibernate 경로(LocalSessionFactoryBean+HibernateTransactionManager)를 제공하며, SpringSessionContext·SpringBeanContainer가 Hibernate의 확장점을 Spring 트랜잭션·DI와 연결한다. 결과적으로 JPA·네이티브 Hibernate·순수 JDBC를 하나의 트랜잭션에서 혼용할 수 있다.