자동구성..근데 이제 Data JPA를 곁들인

YoonJuHo·2025년 3월 17일
post-thumbnail

오늘의 목표

TransactionManager, EntityManagerFactory, Datasource가 언제 자동구성되는지 이해하기

SpringSpring Boot의 여러 차이점 중 자동구성이 존재합니다.
이 글에서는 Data JPA를 사용하는 환경에서는 어떠한 과정을 거쳐 자동구성이 설정되는지 알아보고자 합니다.

Spring vs Spring Boot

Spring은 스프링 프레임워크의 핵심 모듈을 모아서 만든 프레임워크입니다. Spring에서는 개발자가 직접 설정 파일을 작성하여 스프링 컨테이너를 구성하고, 필요한 빈 객체를 등록하고, 빈 객체 간의 의존성을 설정해야 합니다. Spring은 특정한 구성을 위해 추가적인 라이브러리와 설정이 필요합니다.
반면, Spring Boot는 스프링 프레임워크를 보다 쉽게 사용할 수 있도록 만든 프레임워크입니다. Spring Boot에서는 개발자가 설정 파일을 작성할 필요 없이, 프로젝트의 설정과 라이브러리 의존성을 자동으로 처리해주는 기능을 제공합니다.

우선 프로젝트 설정은 아래와 같습니다.

  • Spring Boot 3.2.4
  • dependencies
  • application.properties

라이브러리 훑어보기

spring-boot-starter를 디펜던시로 설정해놓으면 아래와 같이 자동으로 autoconfigure 기능이 추가되는 것을 알 수 있습니다.

spring-boot-starter 를 통해 추가된 autoconfigure 안에서
DataSourceAutoConfiguration를 확인할 수 있는데, DataSourceAutoConfigurationDatasource를 자동으로 등록해주는 클래스입니다.

만약 org.springframework.boot:spring-boot-starter-data-jpa 혹은 org.springframework.boot:spring-boot-starter-jdbc를 의존성에 추가하지 않았다면 아래와 같이 org.springframework.jdbc 를 인식할 수 없어 DataSource를 자동으로 등록하는 자동구성을 이용할 수 없습니다.


DataSourceAutoConfiguration

이 클래스는 애플리케이션에서 데이터베이스 연결을 위한 DataSource 빈을 자동으로 생성하고 구성하는 역할을 담당합니다.

여기서는 크게 3가지 설정들을 살펴보겠습니다.

  • EnableConfigurationProperties({DataSourceProperties.class}) : 외부 설정 파일의 DataSource 관련 프로퍼티를 바인딩합니다.

  • EmbeddedDatabaseCondition : 이 조건은 spring.datasource.url 속성이 존재하지 않거나 비어있을 경우, 내장형 데이터베이스 구성을 활성화하기 위한 조건입니다.

  • @Import를 통해 DataSourceAutoConfiguration는 데이터베이스 연결을 위한 DataSource 빈을 자동으로 생성하기 위한 여러 구현체의 구성을 가져옵니다.

    스프링 부트에서는 데이터베이스 종류와 상관없이 기본 DataSource 구현체로 HikariCP를 사용하기에 아래 Hikari DataSource가 등록됩니다.

현재 상황은 application.yml(application.properties 등)에서 하나의 DataSource만 설정했을 때의 예시이고, 이 경우 Spring BootDataSourceAutoConfiguration이 조건에 맞춰 자동으로 DataSource 빈(예: HikariDataSource 등)을 생성하고 구성합니다.

만약에 DataSource를 2개 이상 설정하게 된다면? -> 해당 블로그 글 참고

다시 본론으로 돌아와서 DataSourceAutoConfiguration을 통해 자동으로 Datasource를 등록해주는 것을 알 수 있었습니다.
이와 같이 Spring Data JPA를 사용하면 HibernateJpaAutoConfigurationJpaRepositoriesAutoConfiguration가 함께 작동하여 TransactionManager, EntityManagerFactoryJPA를 사용하기 위한 기본 빈들을 자동으로 생성·등록합니다.

  • HibernateJpaAutoConfiguration: 이 클래스는 EntityManagerFactory, PlatformTransactionManager 등 JPA 환경에 필요한 핵심 빈들을 자동으로 등록합니다.
  • JpaRepositoriesAutoConfiguration: 이 클래스는 Repository 인터페이스를 자동으로 스캔하고 등록하여, 별도의 구현체 없이도 CRUD 및 커스텀 메서드를 사용할 수 있도록 도와줍니다.

이렇게 두 자동 구성 클래스가 함께 동작함으로써, 개발자는 복잡한 설정 없이 Spring Data JPA 기능을 손쉽게 사용할 수 있습니다.


JpaRepositoriesAutoConfiguration

우선 JpaRepositoriesAutoConfiguration 를 간략히 살펴보자면,
이 클래스는 Spring Boot에서 JPA 기반의 Repository를 자동 구성하기 위한 auto-configuration 클래스로 아래와 같은 애노테이션들이 설정되어 있습니다.

  • @AutoConfiguration(after = {HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class}) : 이 설정은 Hibernate JPATask Execution 관련 자동 구성들이 먼저 완료된 후에 이 설정이 적용되도록 순서를 보장합니다.
  • @ConditionalOnBean({DataSource.class}) : DataSource 빈이 존재해야 이 auto-configuration이 활성화됩니다. 즉, 데이터베이스 연결 설정이 되어 있어야 합니다.
  • @ConditionalOnClass({JpaRepository.class}) : 클래스패스에 JpaRepository 클래스가 있어야 JPA 리포지토리 기능이 활성화됩니다.

Data JPA가 아닌 JPA를 사용하게 된다면 HibernateJpaAutoConfiguration 클래스만을 사용하고, JpaRepositoriesAutoConfiguration 클래스는 사용하지 않습니다.

Data JPA가 아닌 JPA를 사용하려면?

Data JPA가 아닌 JPA를 사용하려면 이와 같이 의존성을 추가해야 하며 기존에 Data JPA가 제공하는 기능 5가지 중 JpaRepository 를 사용하지 못합니다.

  • JPA 사용 (@Entity, EntityManager) -> jakarta.persistence-api 덕분에 JPA 어노테이션과 인터페이스 사용 가능
  • Hibernate를 통한 ORM 매핑 -> hibernate-core가 JPA 구현체 역할
  • Spring Data의 JpaRepository 자동 구현
  • 트랜잭션 처리 (@Transactional) -> spring-boot-starter에 포함된 spring-tx 덕분에 트랜잭션 처리 가능
  • 기본 JDBC 지원 및 설정 -> spring-boot-starter-jdbc가 DataSource, HikariCP 설정 지원

HibernateJpaAutoConfiguration

해당 클래스에서는 EntityManagerFactory, PlatformTransactionManagerJPA 환경에 필요한 핵심 빈들을 자동으로 등록하는 과정이 이루어집니다.
해당 클래스의 애노테이션들을 확인하게 되면 조금은 익숙한(?) 클래스명이 보입니다.

DataSourceAutoConfigurationafter에 지정하는 코드가 보이는데, 해당 DataSourceAutoConfiguration는 초반에 언급했던 DataSource를 자동구성하는 클래스입니다.

// code
package org.springframework.boot.autoconfigure.orm.jpa;

import jakarta.persistence.EntityManager;
import org.hibernate.engine.spi.SessionImplementor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

@AutoConfiguration(
    after = {DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class},
    before = {TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class}
)
@ConditionalOnClass({LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class})
@EnableConfigurationProperties({JpaProperties.class})
@Import({HibernateJpaConfiguration.class})
public class HibernateJpaAutoConfiguration {
    public HibernateJpaAutoConfiguration() {
    }
}

해당 애노테이션에 구성된 설정 값 대해서도 간략히 알고 넘어가겠습니다.

@AutoConfiguration(
	after = {DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class}, 
    before = {TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
  • after: 데이터 소스 및 트랜잭션 매니저 관련 설정이 먼저 완료된 후에 이 구성 클래스가 적용됩니다.
  • before: 이후에 실행되는 트랜잭션 관련 자동 구성보다 먼저 이 구성이 처리되어야 함을 의미합니다.

해당 애노테이션은 HibernateJpaAutoConfiguration가 실행되는 시점을 제어하는데, 이는 JpaTransactionManager 구성과 밀접하게 관련됩니다. 조금 더 자세히 살펴보겠습니다.

  • after 속성: DataSourceAutoConfigurationTransactionManagerCustomizationAutoConfiguration가 먼저 실행되어, 데이터소스와 트랜잭션 매니저에 대한 기본 설정 및 추가 커스터마이징이 완료된 상태여야 합니다.

    • 여기서 주의해야 할 부분은 TransactionManagerCustomizationAutoConfigurationJpaTransactionManager직접 생성하는 곳이 아닙니다.

    • JpaTransactionManager는 주로 JpaBaseConfiguration(그리고 이를 상속받은 HibernateJpaConfiguration)에서 EntityManagerFactory를 기반으로 생성됩니다.

    • TransactionManagerCustomizationAutoConfiguration는 이미 생성된 TransactionManager에 대해 추가적인 커스터마이징을 적용할 수 있도록 돕는 역할을 합니다. JpaTransactionManager의 기본 생성은 HibernateJpaConfiguration에서 진행되고(HibernateJpaAutoConfiguration X), 그 이후에 TransactionManagerCustomizationAutoConfiguration가 존재하면, 이를 이용해 추가적인 설정이나 수정을 적용합니다.

방금 다룬 부분은 밑에서 한번 더 다루니 지금은 그냥
"JpaBaseConfiguration가 JpaTransactionManager를 생성하는구나!" 정도로만 이해하셔도 괜찮습니다.

  • before 속성: TransactionAutoConfigurationDataSourceTransactionManagerAutoConfiguration보다 먼저 실행되어, JPA 관련 빈들(예: EntityManagerFactory, JpaTransactionManager 등)이 등록되어야 합니다.

이러한 순서 덕분에 HibernateJpaAutoConfiguration는 필요한 빈들이 이미 구성된 상태에서 실행되며, 그 후에 전체 트랜잭션 관련 자동 구성이 이루어지도록 보장됩니다.


@ConditionalOnClass({
		LocalContainerEntityManagerFactoryBean.class, 
     	EntityManager.class, 
        SessionImplementor.class})
  • 이 조건은 다음과 같은 클래스들이 클래스패스에 존재할 때만 Hibernate 기반의 JPA 자동 구성이 활성화된다는 것을 의미합니다.
  • LocalContainerEntityManagerFactoryBean: EntityManagerFactory를 생성하는 Spring ORM의 핵심 클래스
  • EntityManager: JPA 표준 인터페이스
  • SessionImplementor: Hibernate의 내부 세션 인터페이스

즉, JPA와 Hibernate 관련 라이브러리가 존재해야 이 자동 구성이 적용됩니다.


@EnableConfigurationProperties({JpaProperties.class})
  • JpaProperties 클래스에 정의된 외부 설정(예: application.properties 또는 application.yml 파일의 spring.jpa.* 관련 설정)이 자동으로 바인딩되어, 개발자가 설정한 값을 기반으로 Hibernate 관련 빈들이 구성됩니다.

@Import({HibernateJpaConfiguration.class})
  • 실제 Hibernate JPA 설정을 담당하는 HibernateJpaConfiguration 클래스를 가져옵니다.
  • 이 클래스에서는 EntityManagerFactory, EntityManager 및 기타 Hibernate 관련 빈들을 등록하는 작업이 이루어집니다.

HibernateJpaConfiguration

스프링에서 자바 설정을 추가할 때 사용하는 Import를 타고 들어가게 된다면 실제 Hibernate JPA 설정을 담당하는 HibernateJpaConfiguration 클래스가 나오게 되는데 여기서 주의해야 할 부분은 @ConditionalOnSingleCandidate(DataSource.class) 입니다.

이 클래스는 @ConditionalOnSingleCandidate(DataSource.class) 조건이 붙어 있어서, DataSource 빈이 단 하나일 때만 자동 구성됩니다.

즉, 여러 개의 DataSource가 등록되어 있으면 이 조건이 충족되지 않아 HibernateJpaConfiguration이 자동으로 동작하지 않습니다.

  • HibernateJpaConfiguration이 동작하지 않는다면 자동으로 JpaTransactionManagerEntityManagerFactory와 같은 JPA 관련 핵심 빈들이 생성되지 않게 됩니다. 따라서 수동으로 해당 설정을 진행해주어야합니다.

수동으로 등록하는 방법 외에도 여러 개의 DataSource가 등록되어 있을 때, 그 중 하나를 @Primary로 지정하면 해당 DataSource가 단일 후보로 간주됩니다. 그러면 @ConditionalOnSingleCandidate(DataSource.class) 조건을 충족하여 HibernateJpaConfiguration가 자동으로 동작하게 됩니다.

JpaBaseConfiguration

해당 클래스(HibernateJpaConfiguration)는 JpaBaseConfiguration 를 상속받고 있습니다. JpaBaseConfiguration 에서 중점적으로 볼 부분은 크게 두가지입니다.

  • 트랜잭션 매니저 빈 등록
  • EntityManagerFactory 빈 등록

트랜잭션 매니저 빈 등록

  • transactionManager 메서드는 JpaTransactionManager를 생성하고, 추가적인 커스터마이징을 적용합니다. 만약 TransactionManager 빈이 없을 경우에만 생성하도록 조건부(@ConditionalOnMissingBean)로 설정되어 있습니다.

EntityManagerFactory 빈 등록

  • entityManagerFactory 메서드에서는 entityManagerFactoryBuilder()를 이용해 LocalContainerEntityManagerFactoryBean을 생성합니다.
    이 빈은 실제로 JPAEntityManagerFactory를 구성하며, 여기서 데이터 소스, 관리 대상 클래스, 추가적인 JPA 속성들이 적용됩니다.

    LocalContainerEntityManagerFactoryBeanSpring Framework에서 제공하는 FactoryBean으로, JPAEntityManagerFactory를 생성하고 구성하는 데 사용됩니다.
    이 클래스를 사용하면 스프링 컨테이너와 통합된 방식으로 JPA 설정을 손쉽게 관리할 수 있으며, 데이터 소스, JPA 벤더 어댑터, 엔티티 패키지 스캔, 추가 JPA 속성 등을 한 곳에서 설정할 수 있습니다.


HibernateJpaConfigurationJpaBaseConfiguration을 통해 우리가 흔히 접하는 JpaTransactionManagerEntityManagerFactory가 자동으로 생성된다는 것을 알 수 있었습니다. 물론 이렇게 내부 흐름을 자세히 알지 않더라도 Spring Data JPA를 사용하는 데 큰 문제는 없지만, 편리하게 사용하는 Spring Boot 내부에는 수많은 자동 구성 메커니즘과 정교한 설계가 숨어 있다는 것을 알 수 있었습니다.

@Component vs @AutoConfiguration 등록 순서는?

개발자가 정의한 Component들이 먼저 스캔 돼서 Bean으로 등록된 후에, AutoConfiguration의 Bean들이 등록됩니다

BeanDefinition이 등록되는 순서는 개발자가 정의한 Component들이 맞지만, 의존성과 BeanFactory 내부 순서에 따라 동시에 생성이 진행됩니다.

순서설명구성 요소
1️⃣@ComponentScan + @Configuration 처리 시작개발자 정의 컴포넌트 클래스들이 스캔되어 BeanDefinition으로 등록됨 (이 시점엔 인스턴스 아님)
2️⃣@EnableAutoConfiguration 처리spring.factories에 정의된 @AutoConfiguration 클래스들도 BeanDefinition으로 등록됨
여기까지는 "정의 등록"만 완료된 시점 (BeanDefinition Registry 단계)→ 즉, 개발자 정의 빈과 AutoConfig 빈은 정의만 먼저 됨
3️⃣Bean 인스턴스 생성 시작이 시점부터 @Component, @Service, AutoConfig 클래스 순서에 따라 실제 객체로 생성됨 (빈 생성 및 주입)
⚠️Bean 생성 순서는 의존성에 따라 달라짐우선순위 지정이 없으면 의존성 그래프 기준으로 생성됨
  • 정의 등록 순서: 개발자 정의 클래스 (@Component 등) → @AutoConfiguration
  • 실제 인스턴스 생성 시점: 의존성과 BeanFactory 내부 순서에 따라 동시에 생성 진행됨
  • "무조건 개발자 정의 빈이 먼저 생성된다"는 말은 정확하지 않음

Spring에서는 같은 타입의 BeanDefinition이 여러 개 등록되어 있을 경우, 기본적으로 사용자 정의 빈이 우선됩니다. 이건 자동 구성(AutoConfiguration) 설계 원칙 중 하나입니다.

  • @ConditionalOnMissingBean : 대부분의 자동 구성 클래스는 이 조건이 붙어 있음. 즉, 동일 타입의 빈이 이미 존재하면 자동 구성은 생략됨
  • BeanDefinition 우선순위 : Spring은 BeanDefinition을 만들 때, 사용자가 등록한 Bean을 자동 구성보다 우선시함
  • 명시적 정의 > 자동 등록 : 개발자가 명시적으로 등록한 빈이 우선 → Spring Boot의 핵심 철학인 "자동이지만 필요하면 덮어써라"를 반영한 설계

💡 그래서 순서를 정리하자면

  • DataSourceAutoConfiguration
    • 애플리케이션의 DataSource 빈을 생성합니다. (데이터베이스 연결, 커넥션 풀 등)
  • TransactionManagerCustomizationAutoConfiguration
    • 기본 트랜잭션 매니저가 생성되기 전에, 필요한 커스터마이징을 적용합니다.
  • HibernateJpaAutoConfiguration
    • 위의 두 단계(DataSource와 트랜잭션 매니저 커스터마이징)가 완료된 후 실행됩니다.
    • 이 단계에서 HibernateJpaAutoConfiguration이 HibernateJpaConfiguration을 임포트합니다.
  • HibernateJpaConfiguration (JpaBaseConfiguration 기반)
    • JpaVendorAdapter를 생성 및 등록하여, Hibernate 등 JPA 공급자 특화 설정을 적용합니다.
    • EntityManagerFactoryBuilder를 생성합니다.
    • LocalContainerEntityManagerFactoryBean를 등록하여 실제 JPA의 EntityManagerFactory를 생성하며, 이 빈은 @Primary로 등록됩니다.
    • PlatformTransactionManager (JpaTransactionManager)를 생성 및 등록합니다.
  • JpaRepositoriesAutoConfiguration
    • Spring Data JPA의 Repository 인터페이스들을 스캔하여, JPA Repository 빈들을 자동으로 등록합니다.
  • TransactionAutoConfiguration
    • 위에서 등록한 트랜잭션 매니저를 기반으로, @Transactional 등의 선언적 트랜잭션 관리를 위한 AOP 설정 및 인프라를 구성합니다.

0개의 댓글