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`를 활용하여 필요한 데이터 소스를 런타임에 선택하는 것입니다.