-
springboot dynamic datasource, 동적 데이터 (AbstractRoutingDataSource)WEB/BACK 2025. 2. 21. 14:41반응형
요구사항
1. 동적으로 db등록
2. 등록한 db에서 쿼리 실행
기존에 application.properties에다가
spring.datasource.url=jdbc:postgresql://x
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.username=
spring.datasource.password=
이런식으로 db정보를 입력해 자동으로spring boot가 bean을 생성하도록 뒀던것과는 다르게
사용자로부터 입력받아서 그 디비에서 쿼리 실행되도록하기
AbstractRoutingDataSource는 Connection이 필요한 시점에 원하는 DataSource로 Routing해주는 DataSource의 구현체이다.
determineCurrentLookupKey
현재 쓰레드에서 설정한 데이터 소스 키를 가져와서 반환
DynamicDataSourceContextHolder.getDataSourceKey()가 **"신한은행 계좌"**라고 하면
determineCurrentLookupKey()는 **"신한은행 계좌를 사용하세요!"**라고 알려줌public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private final Map<Object, Object> targetDataSources = new ConcurrentHashMap<>(); // 초기화 추가 @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceKey(); } @Override public void setTargetDataSources(Map<Object, Object> newDataSources) { // 기존 데이터 소스 유지하면서 새로운 데이터 소스 추가 this.targetDataSources.putAll(newDataSources); super.setTargetDataSources(this.targetDataSources); super.afterPropertiesSet(); } public Map<Object, Object> getTargetDataSources() { return this.targetDataSources; } }
등록하는데 필요한 DTO
public class DataSourceConfigRequest { private String dataSourceName; private String url; private String username; private String password; private String driverClassName; //getter, setter
default db를 하나 지정해준다
@Configuration public class DataSourceConfig { @Primary @Bean(name = "postgresql") public DataSource defaultDataSource() { return DataSourceBuilder.create() .url("jdbc:postgres.....g") .username("....x") .password("....") .driverClassName("org.postgresql.Driver") .build(); } @Bean public DataSource dynamicDataSource(@Qualifier("postgresql") DataSource postgresqlDataSource) { DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("postgresql", postgresqlDataSource); dynamicDataSource.setDefaultTargetDataSource(postgresqlDataSource); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } }
현재 쓰레드에서 사용할 데이터 소스를 저장하고 관리하는 역할을 합니다.
즉, 어떤 데이터 소스를 사용할지 결정하는 키를 ThreadLocal을 이용해 저장하는 것public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); //ThreadLocal<String>을 사용하여 현재 쓰레드에서 사용할 데이터 소스 키를 저장 //예를 들어 setDataSourceKey("test1")을 호출하면 현재 쓰레드는 "test1" 데이터 소스를 사용하게 됩니다. public static void setDataSourceKey(String dataSourceKey) { CONTEXT_HOLDER.set(dataSourceKey); } public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } //요청이 끝난 후 데이터 소스 설정을 초기화하여 다음 요청에서 잘못된 데이터 소스를 참조하는 것을 방지합니다. public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } }
mybatis 설정
@Configuration @MapperScan("com......mapper") public class MyBatisConfig { @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/DataSourceMapper.xml")); return factoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } @Bean public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
controller
db adding
@PostMapping("/addDataSource") public String addDataSource(@RequestBody DataSourceConfigRequest request) { try { DataSource newDataSource = DataSourceBuilder.create() .url(request.getUrl()) .username(request.getUsername()) .password(request.getPassword()) .driverClassName(request.getDriverClassName()) .build(); // 동적으로 데이터소스 추가 Map<Object, Object> targetDataSources = dynamicDataSource.getTargetDataSources(); targetDataSources.put(request.getDataSourceName(), newDataSource); dynamicDataSource.setTargetDataSources(targetDataSources); return "DataSource added successfully."; } catch (Exception e) { return "Error adding DataSource: " + e.getMessage(); } }
{
"dataSourceName": "greenplum",
"url": "jdbc:pivotal:greenplum:/.........e",
"username": ".....",
"password": "....",
"driverClassName": "com.pivotal.jdbc.GreenplumDriver"
}select
@PostMapping("/select") public ResponseEntity<?> selectData(@RequestParam String dataSourceName, @RequestParam String tableName) { try { // 동적 데이터베이스 선택 DynamicDataSourceContextHolder.setDataSourceKey(dataSourceName); List<Map<String, Object>> result = exampleMapper.selectData(tableName); return ResponseEntity.ok(result); // 정상 응답 } catch (Exception e) { // 예외 발생 시 오류 메시지를 JSON으로 반환 Map<String, Object> errorResponse = new HashMap<>(); errorResponse.put("status", "error"); errorResponse.put("message", e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); } finally { // 데이터베이스 선택 해제 DynamicDataSourceContextHolder.clearDataSourceKey(); } }
반응형'WEB > BACK' 카테고리의 다른 글
EFK(ElasticSearch, fluentd, kibana), fluent-logger, springboot (0) 2025.01.15 EFK (Elasticsearch, Fluentd, Kibana) (1) 2025.01.13 java calendar 날짜 계산(만료일 계산) (0) 2025.01.13 aws marketplace 연동 , aws credential 자격증명 설정,entitlements (0) 2024.11.29 query DSL 적용방법정리 & 자동 빌드 (0) 2024.11.29