ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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();
    	    }
    	}
    반응형

    댓글

Designed by Tistory.