제가 맡고있는 SpringBoot 프로젝트에선 JPA를 사용하고 있으며 분산 환경 데이터베이스를 구축하기 위해  AbstractRoutingDataSource 및 Spring Data JPA를 사용하여 Dynamic DataSource 라우팅을 하고 있습니다. 

@Data
@Configuration
@ConfigurationProperties(prefix = "test")
public class NamedDataSources {
   private List<NamedDataSource> namedDataSources;
}
@Getter
@Setter
public class NamedDataSource {
   private String name;
   private HikariConfig hikari;
}
  1. yml 파일에 입력된 db 설정 정보가 NamedDataSources 클래스에 로딩된다.
  2. DataSource를 생성한다. (DetermineRoutingDatasource)
    1. DetermineRoutingDataSource 인스턴스 생성
    2. DatermineRoutingDataSource에는 key,value 형태로 저장된 NamedDataSources 정보가 있다.
    3. Default 데이터소스가 등록된다.
    4. DataSoucre로 DetermineRoutingDataSource가 리턴된다.
  3. EntityManager Bean (LocalContainerEntityManagerFactory)을 등록한다.
    1. Datasource지정-> (2번에서 생성한 DetermineRoutingDataSource)로 설정
    2. Hibernate Property, Entity가 위치한 Package 지정
    3. Hibernate 기반으로 동작하는 것을 지정하는 JPA Vendor 설정
  4. TransactionManager Bean을 등록한다.
    1. LocalContainerEntityManagerFactory Bean을 주입받음
    2. Datasource와 EntityManagerFactoryBean에서 생성되는 EntityManagerFactory를 지정

 

@Configuration
@EnableJpaRepositories(basePackages = "com.xx.xxx.xx.xx",
        transactionManagerRef = "transcationManager",
        entityManagerFactoryRef = "entityManager")
@EnableTransactionManagement
public class NamedRoutingDataSources {

    private final TestProperties jpaProps;
    private final NamedDataSources namedDataSources;

    public NamedRoutingDataSources(TestProperties jpaProps, NamedDataSources namedDataSources) {
        this.jpaProps = jpaProps;
        this.namedDataSources = namedDataSources;
    }

    @Primary
    @Bean
    public DataSource createRoutingDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();

        for (NamedDataSource namedDataSource : namedDataSources.getNamedDataSources()) {
            targetDataSources.put(DatabaseCluster.valueOf(namedDataSource.getName()), new HikariDataSource(namedDataSource.getHikari()));
        }

        DetermineRoutingDataSource routingDataSource = new DetermineRoutingDataSource();
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(targetDataSources.get(DatabaseCluster.MZ));

        return routingDataSource;
    }

    @Bean(name = "entityManager")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(createRoutingDataSource());
        factoryBean.setPackagesToScan("com.xx.xxx.xx.xx");
        factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        factoryBean.setJpaProperties(initJpaHibernateProperties());
        return factoryBean;
    }

    private Properties initJpaHibernateProperties() {
        Properties properties = new Properties();
        properties.put(AvailableSettings.FORMAT_SQL, jpaProps.getProperties().getHibernate().isFormatSql());
        properties.put(AvailableSettings.SHOW_SQL, jpaProps.getProperties().getHibernate().isShowSql());
        return properties;
    }

    @Bean(name = "transcationManager")
    public JpaTransactionManager transactionManager(
            @Autowired @Qualifier("entityManager") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        return new JpaTransactionManager(entityManagerFactoryBean.getObject());
    }
}

SpringBoot 프로젝트가 로딩 될 때 위와 같이 설정이 됩니다.

크게 보자면 먼저 Datasource를 생성하고, Spring 프로젝트에서 JPA를 사용하기 위해 EntityManager를 설정합니다. 그리고 Spring Container에서 동작하는 JPA의 기능을 활용하고, 스프링이 제공하는 일관성 있는 데이터 액세스 기술의 접근 방법을 적용할 수 있도록 LocalContainerEntityManager를 생성합니다.

 

 

AbstractRoutingDataSource는 조회 키를 기반으로 다양한 대상 데이터 소스 중 하나로 호출을 라우팅하는 DataSource의 추상 구현체입니다. AbstractRoutingDataSource는 현재 컨텍스트를 기반으로 실제 데이터 소스를 동적으로 결정하는 방법을 제공하기 위해 Spring 2.0.1 버전에 도입되었습니다. 컨텍스트 변경을 통해 전환되는 여러 데이터 소스의 맵을 유지 관리합니다.

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DetermineRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TestThreadLocal.getDatabaseCluster();
    }
}
import org.springframework.stereotype.Component;

@Component
public class TestThreadLocal {

    public static final ThreadLocal<DatabaseCluster> contextHolder = new ThreadLocal<>();

    public static DatabaseCluster getDatabaseCluster() {
        return contextHolder.get();
    }

    public static void setDatabaseCluster(DatabaseCluster databaseCluster) {
        contextHolder.set(databaseCluster);
    }

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

https://www.websparrow.org/spring/spring-boot-dynamic-datasource-routing-using-abstractroutingdatasource

 

+ Recent posts