Skip to content

Latest commit

 

History

History
162 lines (130 loc) · 12.2 KB

File metadata and controls

162 lines (130 loc) · 12.2 KB

02. EntityManagerFactory 부트스트랩

무엇을 / 왜

JPA를 쓰려면 먼저 EntityManagerFactory(EMF)를 만들어야 한다. 순수 JPA는 META-INF/persistence.xml을 읽어 Persistence.createEntityManagerFactory("unit")를 호출하는 방식이다. 그러나 이렇게 만든 EMF는 Spring의 DataSource·엔티티 스캔·예외 변환과 연결되지 않고, 트랜잭션 EM 공유 기능도 없다.

이 챕터는 Spring이 EMF를 FactoryBean으로 만들고 프록시로 감싸서 싱글톤 빈으로 노출하는 과정을 다룬다. 관련 타입은 orm.jpaAbstractEntityManagerFactoryBean·LocalContainerEntityManagerFactoryBean·EntityManagerFactoryInfoorm.jpa.persistenceunit 패키지 전체다.

핵심 타입

            ┌──────────────────────────────────────────────┐
            │ AbstractEntityManagerFactoryBean              │
            │  implements SmartFactoryBean<EMF>,            │
            │             EntityManagerFactoryInfo,         │
            │             PersistenceExceptionTranslator,   │
            │             InitializingBean, DisposableBean  │
            │  - 공통: 프록시 생성, 벤더어댑터 적용,         │
            │          비동기 부트스트랩, 예외변환           │
            │  - abstract createNativeEntityManagerFactory()│
            └───────────────┬──────────────────────────────┘
                            │ extends
          ┌─────────────────┴───────────────────┐
          ▼                                      ▼
 ┌────────────────────────┐         ┌──────────────────────────┐
 │ LocalEntityManager-    │         │ LocalContainerEntity-     │
 │ FactoryBean            │         │ ManagerFactoryBean        │
 │ - SE 스타일            │         │ - 컨테이너 스타일(권장)    │
 │ - Persistence.create-  │         │ - PersistenceUnitInfo로   │
 │   EntityManagerFactory │         │   provider.createContainer│
 │ - DataSource 주입 불가 │         │ - DataSource/스캔/LTW 통합│
 └────────────────────────┘         └─────────────┬────────────┘
                                                   │ 사용
                                     ┌─────────────▼────────────┐
                                     │ orm.jpa.persistenceunit  │
                                     │ DefaultPersistenceUnit-  │
                                     │ Manager                  │
                                     │ → PersistenceUnitInfo    │
                                     └──────────────────────────┘
  • AbstractEntityManagerFactoryBean: 모든 부트스트랩의 공통 골격. native EMF를 만든 뒤 프록시로 감싸고, 공유 EM·EntityAgent까지 노출한다. PersistenceExceptionTranslator도 구현하므로 그 존재만으로 예외 변환이 활성화된다.
  • LocalContainerEntityManagerFactoryBean(이하 LCEMFB): 실무 표준. persistence.xml을 직접 파싱하거나 패키지 스캔으로 PersistenceUnitInfo를 조립하고, Spring DataSource·LoadTimeWeaver를 거기에 끼워 넣은 뒤 PersistenceProvider.createContainerEntityManagerFactory()를 호출한다.
  • EntityManagerFactoryInfo: Spring이 만든 EMF 프록시에 캐스팅하면 얻는 메타데이터 인터페이스. getNativeEntityManagerFactory(), getDataSource(), getJpaDialect(), createNativeEntityManager() 등을 노출한다. 트랜잭션 매니저·EM 생성기들이 이 인터페이스로 프록시에서 정보를 캐낸다.

동작 흐름 1 — afterPropertiesSet()이 EMF를 짓는 과정

AbstractEntityManagerFactoryBean.afterPropertiesSet()(소스 367행~)이 부트스트랩의 심장이다.

 afterPropertiesSet()
   │
   ├─ 1. JpaVendorAdapter 적용 (있으면)
   │     persistenceProvider, jpaPropertyMap, EM/EMF 인터페이스,
   │     entityAgentInterface, jpaDialect 를 어댑터 기본값으로 채움
   │
   ├─ 2. native EMF 생성
   │     bootstrapExecutor 있으면 → Future로 백그라운드 생성
   │     없으면 → buildNativeEntityManagerFactory() 동기 실행
   │              └─ createNativeEntityManagerFactory()  (하위 클래스)
   │              └─ jpaVendorAdapter.postProcessEntityManagerFactory()
   │
   ├─ 3. EMF 프록시 생성  createEntityManagerFactoryProxy(native)
   │     → 모든 EMF 인터페이스 + EntityManagerFactoryInfo 를 구현하는 JDK 프록시
   │
   ├─ 4. 공유 EntityManager 프록시 생성
   │     SharedEntityManagerCreator.createSharedEntityManager(프록시)
   │
   └─ 5. (JPA 4.0) 공유 EntityAgent 프록시 생성 (있으면)

여기서 1단계의 벤더 어댑터 병합이 핵심이다. HibernateJpaVendorAdapter를 꽂아두면 provider 클래스, Hibernate 프로퍼티, HibernateJpaDialect가 자동으로 채워진다(05장). 사용자가 명시한 값이 우선이고 빈 칸만 어댑터가 채운다(if (!this.jpaPropertyMap.containsKey(key))).

2단계의 비동기 부트스트랩: setBootstrapExecutor()로 Executor를 주면 EMF 초기화를 백그라운드 스레드에서 돌리고, 프록시는 즉시 반환한다. 첫 실제 호출이 들어오면 getNativeEntityManagerFactory()Future.get()으로 블로킹한다. 단, 6.2부터는 afterSingletonsInstantiated()에서 컨텍스트 리프레시 완료 전에 강제로 완료를 기다린다(소스 427행).

동작 흐름 2 — EMF 프록시가 createEntityManager()를 가로채는 이유

3단계에서 만든 프록시는 단순 위임이 아니다. ManagedEntityManagerFactoryInvocationHandlerinvokeProxyMethod()(소스 512행)가 호출을 분기한다.

 emfProxy.someMethod(args)
   │
   ├─ EntityManagerFactoryInfo 메서드?  → this(FactoryBean)에 위임
   │     (getNativeEntityManagerFactory, getDataSource, getJpaDialect ...)
   │
   ├─ createEntityManager(SYNCHRONIZED, ...) ?
   │     → native EMF로 raw EM 생성 → postProcessEntityManager()
   │     → ExtendedEntityManagerCreator.createApplicationManagedEntityManager(
   │           raw, this, synchronized=true)   ← "확장 EM 프록시" 반환
   │
   ├─ 인자에 Query 프록시 있으면 unwrap (addNamedQuery 대비)
   │
   └─ 그 외 → native EMF에 위임. 반환이 EntityManager면
              역시 확장 EM 프록시로 감싸서 반환

이 프록시를 거쳐 만든 모든 EM은 "확장 EM 프록시"가 되어 진행 중인 트랜잭션에 자동 합류한다(03장). 애플리케이션이 직접 emf.createEntityManager()를 호출해도 Spring 트랜잭션을 인식하는 EM이 나오는 이유다.

동작 흐름 3 — LCEMFB의 persistence unit 조립

LocalContainerEntityManagerFactoryBean.afterPropertiesSet()(소스 383행)은 부모 호출 전에 PersistenceUnitInfo를 준비한다.

 LCEMFB.afterPropertiesSet()
   │
   ├─ PersistenceUnitManager 결정
   │     외부 주입 없으면 internalPersistenceUnitManager(DefaultPersistenceUnitManager) 사용
   │     internal.afterPropertiesSet()  ← persistence.xml 스캔 + 엔티티 스캔
   │
   ├─ determinePersistenceUnitInfo(manager)
   │     unitName 있으면 obtainPersistenceUnitInfo(name)
   │     없으면 obtainDefaultPersistenceUnitInfo()
   │
   ├─ SmartPersistenceUnitInfo면 provider 루트 패키지 설정
   ├─ qualifier 어노테이션 → 빈 정의에 AutowireCandidateQualifier 추가
   │
   └─ super.afterPropertiesSet()  ← 공통 골격(흐름 1)
         └─ createNativeEntityManagerFactory()  (아래)

그리고 native EMF 생성(소스 422행)은:

 createNativeEntityManagerFactory()
   ├─ PersistenceProvider 결정 (명시값 → 없으면 PUI의 providerClassName 인스턴스화)
   └─ provider.createContainerEntityManagerFactory(persistenceUnitInfo, jpaPropertyMap)

setDataSource()/setJtaDataSource()(소스 313/331행)는 DefaultPersistenceUnitManagerSingleDataSourceLookup과 기본 DataSource를 설정한다. 그래서 persistence.xml에 JDBC 설정을 적지 않고 Spring DataSource 빈을 그대로 쓸 수 있다 — 이것이 LCEMFB가 SE 스타일 LocalEntityManagerFactoryBean보다 강력한 핵심 이유다.

persistenceunit 패키지와 엔티티 스캔

DefaultPersistenceUnitManager는 다음을 담당한다(소스 56~91행).

  • persistence.xml 스캔: 기본 위치 classpath*:META-INF/persistence.xmlPersistenceUnitReader로 파싱해 MutablePersistenceUnitInfo를 만든다.
  • Spring 기반 엔티티 스캔: setPackagesToScan(...)을 쓰면 persistence.xml 없이도 @Entity 클래스를 클래스패스에서 찾는다. 내부적으로 PersistenceManagedTypesScanner가 동작.
  • PersistenceManagedTypes: 관리 대상 클래스명/패키지명 목록을 표현하는 인터페이스. setManagedTypes()로 미리 만든 목록을 주면 런타임 스캔을 건너뛴다.

AOT / 네이티브 이미지 최적화

PersistenceManagedTypesBeanRegistrationAotProcessor(소스 68행)는 빌드 타임에 PersistenceManagedTypes 빈을 발견하면, 런타임 스캔 대신 하드코딩된 클래스명/패키지명 목록으로 치환하는 코드를 생성한다. 동시에 각 엔티티에 대해 리플렉션 힌트(생성자·@EntityListeners·@Converter·콜백 메서드 등)를 등록해, GraalVM 네이티브 이미지에서 JPA가 동작하도록 한다. 즉 "스캔은 빌드 타임에 한 번, 런타임엔 결과만"이라는 AOT 원칙을 JPA에 적용한 것이다.

핵심 메서드 정리

메서드 위치 하는 일
afterPropertiesSet() AbstractEntityManagerFactoryBean 벤더어댑터 병합 → native EMF 생성 → 프록시·공유 EM 생성
createEntityManagerFactoryProxy() EMF 인터페이스 + EntityManagerFactoryInfo를 구현하는 JDK 프록시 생성
invokeProxyMethod() createEntityManager 가로채 확장 EM 프록시로 변환, Info 메서드 분기
createNativeEntityManager() raw EM 생성 + postProcessEntityManager 적용 (트랜잭션 매니저가 호출)
createNativeEntityManagerFactory() LocalContainerEMFB provider.createContainerEntityManagerFactory 호출
determinePersistenceUnitInfo() unit 이름으로 PUI 선택(또는 디폴트)

설계 포인트

  • 프록시 이중화: EMF를 프록시로 감싸 (a) EntityManagerFactoryInfo 믹스인을 주입하고 (b) 거기서 나오는 EM을 자동으로 트랜잭션 인식 프록시로 만든다. 부트스트랩 단계에서 이 두 장치를 심어 두기에, 이후 모든 EM 사용이 투명하게 Spring 트랜잭션과 연동된다.
  • SmartFactoryBean로 EM 직접 주입: 7.0부터 EMF 팩토리빈이 공유 EntityManager도 노출한다(getObject(EntityManager.class)). 덕분에 @Autowired EntityManager가 가능해졌다.
  • 확장점: postProcessEntityManagerFactory(emf, pui)(LCEMFB), setEntityManagerInitializer()(모든 새 EM 커스터마이즈), PersistenceUnitPostProcessor(PUI에 엔티티/jar 추가).

정리

EMF 부트스트랩은 AbstractEntityManagerFactoryBean이 공통 골격을, LocalContainerEntityManagerFactoryBean이 컨테이너 스타일 조립을 맡는 2층 구조다. 핵심 산출물은 EntityManagerFactoryInfo를 겸하는 EMF 프록시이며, 이 프록시가 만들어 내는 EM은 자동으로 트랜잭션을 인식한다. persistenceunit 패키지는 persistence.xml 파싱과 엔티티 스캔, 그리고 AOT 최적화를 담당해, 런타임 의존을 최소화한다.