Spring Boot 다중 데이터 소스 및 동적 데이터 소스 구성

1. Spring Boot에서 다중 데이터 소스 구현하기

1.1 의존성 추가

MySQL과 JPA를 사용한 예제입니다.
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

1.2 설정 파일 작성

# application.yml
spring:
  datasource:
    main:
      url: jdbc:mysql://localhost:3306/db1
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    auxiliary:
      url: jdbc:mysql://localhost:3306/db2
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

1.3 데이터 소스 설정 클래스

메인 및 보조 데이터 소스에 대해 각각 `DataSource`, `EntityManagerFactory`, `TransactionManager`를 구성합니다.

메인 데이터 소스 설정

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    basePackages = "com.example.repository.main",
    entityManagerFactoryRef = "mainEntityManagerFactory",
    transactionManagerRef = "mainTransactionManager"
)
public class MainDataSourceConfig {
    
    @Primary
    @Bean(name = "mainDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.main")
    public DataSource mainDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "mainEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean mainEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("mainDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.example.entity.main")
                .persistenceUnit("main")
                .build();
    }

    @Primary
    @Bean(name = "mainTransactionManager")
    public PlatformTransactionManager mainTransactionManager(
            @Qualifier("mainEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

보조 데이터 소스 설정

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    basePackages = "com.example.repository.auxiliary",
    entityManagerFactoryRef = "auxiliaryEntityManagerFactory",
    transactionManagerRef = "auxiliaryTransactionManager"
)
public class AuxiliaryDataSourceConfig {

    @Bean(name = "auxiliaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.auxiliary")
    public DataSource auxiliaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "auxiliaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean auxiliaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("auxiliaryDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.example.entity.auxiliary")
                .persistenceUnit("auxiliary")
                .build();
    }

    @Bean(name = "auxiliaryTransactionManager")
    public PlatformTransactionManager auxiliaryTransactionManager(
            @Qualifier("auxiliaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

2. 동적 데이터 소스 구현

2.1 동적 데이터 소스 개요

동적 데이터 소스는 실행 중 특정 비즈니스 요구 사항에 따라 데이터 소스를 전환하는 것을 의미합니다. 주요 응용 분야는 다음과 같습니다: - 멀티 테넌트(Multi-Tenant) 애플리케이션 - 읽기/쓰기 분리 - 비즈니스별 데이터베이스 분리

2.2 구현 방법

Spring Boot의 기본 데이터 소스는 정적으로 작동하므로, 동적 데이터 소스를 위해 다음 단계를 따릅니다.

2.2.1 데이터 소스 컨텍스트 정의

public class DatabaseContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDatabase(String databaseKey) {
        contextHolder.set(databaseKey);
    }

    public static String getDatabase() {
        return contextHolder.get();
    }

    public static void clear() {
        contextHolder.remove();
    }
}

2.2.2 동적 데이터 소스 라우팅

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDatabase();
    }
}

2.2.3 다중 데이터 소스 구성

@Configuration
public class DatabaseConfig {

    @Bean
    public DataSource dataSource() {
        DataSource primary = DataSourceBuilder.create().url(...).build();
        DataSource secondary = DataSourceBuilder.create().url(...).build();

        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primary);
        targetDataSources.put("secondary", secondary);

        DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
        dynamicDataSource.setDefaultTargetDataSource(primary);
        dynamicDataSource.setTargetDataSources(targetDataSources);

        return dynamicDataSource;
    }
}

2.2.4 데이터 소스 전환을 위한 AOP 적용

주석 정의
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DBSwitch {
    String value() default "primary";
}
AOP 구현
@Aspect
@Component
public class DynamicDatabaseAspect {

    @Before("@annotation(dbSwitch)")
    public void switchDatabase(DBSwitch dbSwitch) {
        DatabaseContextHolder.setDatabase(dbSwitch.value());
    }

    @After("@annotation(dbSwitch)")
    public void resetDatabase(DBSwitch dbSwitch) {
        DatabaseContextHolder.clear();
    }
}
사용 예제
@Service
public class UserHandler {

    @DBSwitch("secondary")
    public void performOnSecondaryDB() {
        // 여기서는 자동으로 secondary 데이터 소스로 전환됩니다.
    }
}

3. 주요 개념 설명

3.1 AbstractRoutingDataSource 원리

Spring의 `AbstractRoutingDataSource`는 동적 데이터 소스의 핵심 역할을 합니다. - 이 클래스의 `determineCurrentLookupKey()` 메소드가 현재 사용할 데이터 소스를 결정합니다. - 데이터베이스 작업마다 이 메소드가 호출되어 적절한 데이터 소스를 선택합니다.

3.2 트랜잭션 관리

동적 데이터 소스에서도 Spring은 트랜잭션 시작 시 데이터 소스를 고정하며, 트랜잭션 내에서는 더 이상 변경되지 않습니다(수동 전환 제외).

4. 요약

Spring Boot에서 다중 및 동적 데이터 소스를 구현하는 핵심은 `AbstractRoutingDataSource + ThreadLocal + AOP`를 활용하여 필요한 데이터 소스를 런타임에 선택하는 것입니다.

태그: SpringBoot SpringDataJPA DynamicDataSource

8월 8일 02:05에 게시됨