본문으로 건너뛰기
김신건의 로그

[JPA] PersistenceUnit: EntityManagerFactory의 정의 단위

· 수정 · 📖 약 2분 · 861자/단어 #jpa #hibernate #persistence-unit #entity-manager-factory #configuration
PersistenceUnit, @PersistenceUnit, EntityManagerFactory, persistence.xml, LocalContainerEntityManagerFactoryBean, JPA 영속성 유닛, persistence unit

정의

PersistenceUnit하나의 논리적 DB 연결 + entity 매핑 집합 을 정의한 단위. 결과물은 EntityManagerFactory 1개.

JPA 아키텍처의 계층:

PersistenceUnit  ← 설정 단위 (persistence.xml 또는 Spring config)
   ↓ 생성
EntityManagerFactory  ← 무거운 thread-safe 객체, 앱 1개
   ↓ createEntityManager()
EntityManager  ← 가벼운 단일 thread 객체, 트랜잭션마다 새로
   ↓ 보유
PersistenceContext  ← 1차 캐시 + dirty checking

spring-jpa-persistence-context 가 트랜잭션 범위의 메모리 저장소를 다룬다면, 이 페이지는 앱 시작 시점의 설정 / factory 단위 를 다룬다.

비교 페이지: spring-jpa-context-vs-unit.

persistence.xml (표준 Jakarta Persistence)

순수 JPA 환경의 정석:

<!-- src/main/resources/META-INF/persistence.xml -->
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.1">
    <persistence-unit name="primaryPU" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>com.example.User</class>
        <class>com.example.Order</class>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:postgresql://localhost/db"/>
            <property name="jakarta.persistence.jdbc.user" value="app"/>
            <property name="jakarta.persistence.jdbc.password" value="secret"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
            <property name="hibernate.hbm2ddl.auto" value="validate"/>
            <property name="hibernate.show_sql" value="false"/>
        </properties>
    </persistence-unit>
</persistence>
EntityManagerFactory emf = Persistence.createEntityManagerFactory("primaryPU");
EntityManager em = emf.createEntityManager();

Spring Boot 환경에선 직접 작성 거의 안 함. Spring 이 LocalContainerEntityManagerFactoryBean 으로 동등한 기능 제공.

Spring Boot 의 PersistenceUnit (자동)

Spring Boot 의 JpaAutoConfiguration 이 자동 생성:

spring:
  datasource:
    url: jdbc:postgresql://localhost/db
    username: app
    password: ${DB_PASSWORD}
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
    hibernate:
      ddl-auto: validate
    show-sql: false

내부적으로 LocalContainerEntityManagerFactoryBean Bean 자동 등록. persistence.xml 없이도 entity 자동 스캔 (@SpringBootApplication 패키지 기준).

명시적 등록:

@Configuration
public class JpaConfig {
    @Bean
    LocalContainerEntityManagerFactoryBean entityManagerFactory(
            DataSource dataSource,
            EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(dataSource)
            .packages("com.example.domain")
            .persistenceUnit("primary")
            .properties(Map.of(
                "hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect",
                "hibernate.hbm2ddl.auto", "validate"
            ))
            .build();
    }

    @Bean
    PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

transaction-type: RESOURCE_LOCAL vs JTA

<persistence-unit name="..." transaction-type="RESOURCE_LOCAL">
<persistence-unit name="..." transaction-type="JTA">
타입트랜잭션 관리사용처
RESOURCE_LOCALJPA 가 직접 (em.getTransaction().begin/commit)일반 Spring Boot, 단일 DB
JTA외부 Transaction Manager (Java EE container, Atomikos, Narayana)XA 분산 트랜잭션, EJB

Spring Boot 의 기본은 RESOURCE_LOCAL + JpaTransactionManager (Spring 의 transaction abstraction). JTA 는 별도 설정 필요 + 거의 안 씀.

@PersistenceUnit 어노테이션

EntityManagerFactory 자체를 주입받을 때:

@Service
public class MyService {
    @PersistenceUnit(unitName = "primary")
    private EntityManagerFactory emf;

    public void doWork() {
        EntityManager em = emf.createEntityManager();
        try {
            em.getTransaction().begin();
            // ...
            em.getTransaction().commit();
        } finally {
            em.close();
        }
    }
}

거의 안 씀. Spring Boot 에선 @PersistenceContext 또는 Repository 패턴 권장.

@PersistenceUnit vs @PersistenceContext:

  • @PersistenceUnitEntityManagerFactory 주입 (factory)
  • @PersistenceContextEntityManager 주입 (PC handle, 트랜잭션 scope)

복수 PersistenceUnit

서로 다른 DB / entity set 사용 시:

@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.orders",
    entityManagerFactoryRef = "ordersEmf",
    transactionManagerRef = "ordersTm"
)
public class OrdersJpaConfig {
    @Bean
    LocalContainerEntityManagerFactoryBean ordersEmf(
            EntityManagerFactoryBuilder builder,
            @Qualifier("ordersDataSource") DataSource ds) {
        return builder.dataSource(ds)
            .packages("com.example.orders")
            .persistenceUnit("orders")
            .build();
    }

    @Bean
    PlatformTransactionManager ordersTm(@Qualifier("ordersEmf") EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.analytics",
    entityManagerFactoryRef = "analyticsEmf",
    transactionManagerRef = "analyticsTm"
)
public class AnalyticsJpaConfig {
    @Bean
    LocalContainerEntityManagerFactoryBean analyticsEmf(
            EntityManagerFactoryBuilder builder,
            @Qualifier("analyticsDataSource") DataSource ds) {
        return builder.dataSource(ds)
            .packages("com.example.analytics")
            .persistenceUnit("analytics")
            .build();
    }

    @Bean
    PlatformTransactionManager analyticsTm(@Qualifier("analyticsEmf") EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

각 entity 패키지는 분리. @Repository 도 각 패키지 안. spring-boot-multi-datasource 참고.

EntityManagerFactory 의 무게

EntityManagerFactory emf = Persistence.createEntityManagerFactory("primaryPU");

내부에서 일어나는 일:

  • 모든 @Entity 클래스 스캔 + 메타데이터 분석
  • mapping XML 파싱 (있다면)
  • second-level cache 초기화
  • query 사전 컴파일 (named queries)
  • connection pool 초기화

비싸다. 앱 시작 시 1번만 생성. close 안 하면 JVM 종료까지 살아 있음. 절대 createEntityManagerFactory 를 반복 호출하지 말 것.

EntityManager 는 가벼움 (PC 1개 + Connection 1개 reference). 트랜잭션마다 새로 생성하고 즉시 close.

Bootstrap Mode

spring:
  jpa:
    bootstrap-mode: deferred   # default / lazy / deferred
Mode동작사용처
default시작 시 동기적으로 EMF 생성보장된 startup, 작은 앱
lazy첫 사용 시점에 생성시작 시간 단축, 첫 요청 느림
deferred백그라운드 thread 로 비동기 생성, 사용 시점에 대기Spring Boot 권장 default

deferred 가 보통 최선. 대형 앱 (entity 수백개) 의 시작 시간 단축 + 첫 요청 안 막힘.

Properties (Hibernate 튜닝)

spring:
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
        use_sql_comments: true
        jdbc:
          batch_size: 50            # batch INSERT/UPDATE
          batch_versioned_data: true
          time_zone: UTC
          fetch_size: 100
        order_inserts: true
        order_updates: true
        connection:
          provider_disables_autocommit: true
        cache:
          use_second_level_cache: true
          region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
        query:
          plan_cache_max_size: 2048
          plan_parameter_metadata_max_size: 128
        statistics: false              # 운영 환경에선 보통 false

자주 쓰는 옵션:

  • batch_size: 50 : INSERT/UPDATE batch 처리 (성능 큰 영향)
  • order_inserts: true : batch hit 률 ↑
  • provider_disables_autocommit: true : HikariCP 와 조합 시 connection 획득 최적화
  • format_sql, use_sql_comments : dev 만, 운영은 비활성

Entity 스캔 범위

@Bean
LocalContainerEntityManagerFactoryBean emf(EntityManagerFactoryBuilder builder, DataSource ds) {
    return builder.dataSource(ds)
        .packages("com.example.domain", "com.example.shared")   // 명시
        .persistenceUnit("primary")
        .build();
}

@EntityScan 어노테이션도 가능:

@SpringBootApplication
@EntityScan(basePackages = "com.example.domain")
class App {}

명시 안 하면 @SpringBootApplication 패키지 + 하위만.

Persistence Provider

JPA 는 spec, 실제 구현은 provider:

spring:
  jpa:
    properties:
      hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
Provider특징
Hibernate (기본)가장 일반, Spring Boot 의 기본
EclipseLinkJakarta EE reference impl
OpenJPAApache, deprecated 흐름

대부분 Hibernate. 다른 provider 교체는 거의 안 일어남.

함정과 베스트 프랙티스

  • EntityManagerFactory 는 앱 1개: 반복 생성 = 메모리 누수
  • Spring Boot 에선 persistence.xml 거의 불필요: LocalContainerEntityManagerFactoryBean 이 동등
  • bootstrap-mode: deferred 권장: 시작 시간 단축
  • hibernate.jdbc.batch_size 설정: 대량 INSERT 시 큰 효과
  • 여러 PersistenceUnit 사용 시 패키지 분리 필수: entity 가 어느 PU 에 속하는지 명확히
  • @PersistenceUnit vs @PersistenceContext 혼동 주의: 후자가 보통 정답
  • transaction-type 은 RESOURCE_LOCAL: JTA 는 특수 상황만
  • 2차 캐시 신중: 멀티 인스턴스에서 stale 위험, 정말 read-heavy 한 항목만

관련 위키

이 글의 용어 (7개)
[JPA] PersistenceContext vs PersistenceUnit 비교spring
정의 JPA 의 두 개념은 레벨이 다르다. 이름이 비슷해서 자주 혼동하지만: - PersistenceUnit = 설정 단위 (정적, 앱 1번) - PersistenceContex…
[JPA] PersistenceContext: 1차 캐시, dirty checking, flushspring
정의 PersistenceContext 는 가 관리하는 entity 의 메모리 저장소 + 변경 추적기. 트랜잭션 안에서만 살아 있다 ( 진입 시 생성, commit/rollbac…
[Spring Boot] Database Initialization: schema.sql, Flyway, Liquibasespring
정의 DB 스키마 / 데이터를 application 시작 시점에 자동 설정하는 메커니즘. 세 가지 접근: 1. / (Spring Boot 내장, 단순) 2. Hibernate (…
[Spring Boot] Hibernate Naming Strategyspring
정의 Hibernate 가 entity 클래스 / 필드명 → DB table / column 명으로 변환하는 규칙. 두 단계로 분리: | 단계 | 역할 | 인터페이스 | |---…
[Spring Boot] Multi-DataSource: 복수 DB 연결과 트랜잭션spring
정의 한 Spring Boot 앱이 여러 DB / 여러 스키마 에 접근. 패턴 세 가지: 1. 물리적으로 분리된 DB (orders DB + analytics DB) 2. Rea…
[Spring] Spring Data JPA: Repository, Query Methodsspring
정의 Spring Data JPA는 JPA(Hibernate) 위에 Repository 추상화를 얹어 boilerplate를 제거. 인터페이스만 정의하면 Spring이 구현체 자…
[Spring] Transaction: @Transactional, propagation, isolationspring
정의 Spring 트랜잭션은 AOP 기반 선언형 트랜잭션 관리. 한 줄로 메서드 전체를 트랜잭션 범위로. JDBC, JPA, MongoDB 등 backend에 동일 API. 기본…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기