동적 다중 데이터소스, sqlSession 작업중

main
semin.baek 11 months ago
parent 8a56c9a3af
commit 7babf9653e

@ -38,6 +38,12 @@ dependencies {
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0'
implementation group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.12.0'
implementation group: 'org.postgresql', name: 'postgresql', version: '42.7.4'
implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '3.5.2'
implementation group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: '12.8.1.jre8'
implementation group: 'com.ibm.db2', name: 'jcc', version: '12.1.0.0'
}
tasks.named('test') {

@ -1,21 +1,29 @@
package com.bsmlab.dfx.agent.config;
import com.bsmlab.dfx.agent.config.datasource.DataSourceDto;
import com.bsmlab.dfx.agent.config.datasource.DynamicRoutingDataSource;
import com.bsmlab.dfx.agent.config.datasource.RefreshableSqlSessionFactoryBean;
import io.micrometer.common.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.dbcp2.BasicDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScans;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import java.util.Map;
@Slf4j
@Configuration
@RequiredArgsConstructor
@MapperScans({
@MapperScan("com.bsmlab.dfx.agent")
})
public class DfxAgentConfiguration {
public final ApplicationContext applicationContext;
// java -jar dfxagent.jar --embedded.db.file.directory=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples --setting.file=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples\dfxagent.json
// embedded.db.file.directory=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples
@ -43,12 +51,49 @@ public class DfxAgentConfiguration {
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean () {
public SqlSessionFactoryBean sqlSessionFactoryBean(Settings settings) {
RefreshableSqlSessionFactoryBean sqlSessionFactoryBean = new RefreshableSqlSessionFactoryBean();
//setMapperLocations(Resource[] mapperLocations)
Settings settings = applicationContext.getBean(Settings.class);
Resource[] mapperLocations = settings.getMapperLocations();
sqlSessionFactoryBean.setMapperLocations(mapperLocations);
return sqlSessionFactoryBean;
}
@Bean
public DynamicRoutingDataSource dynamicRoutingDataSource(Settings settings) {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<String, DataSourceDto> dataSourceDtoMap = settings.getDataSourceDtoMap();
for(String dataSourceId : dataSourceDtoMap.keySet()) {
DataSourceDto dataSourceDto = dataSourceDtoMap.get(dataSourceId);
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(dataSourceDto.getDriverClassName());
dataSource.setUrl(dataSourceDto.getUrl());
dataSource.setUsername(dataSourceDto.getUsername());
dataSource.setPassword(dataSourceDto.getPassword());
if(oracle.jdbc.driver.OracleDriver.class.getCanonicalName().equals(dataSourceDto.getDriverClassName())) {
dataSource.setValidationQuery("SELECT 1 FROM DUAL");
}
else if(org.postgresql.Driver.class.getCanonicalName().equals(dataSourceDto.getDriverClassName())) {
dataSource.setValidationQuery("SELECT 1");
}
else if(com.mysql.jdbc.Driver.class.getCanonicalName().equals(dataSourceDto.getDriverClassName())) {
dataSource.setValidationQuery("SELECT 1");
}
else if(org.mariadb.jdbc.Driver.class.getCanonicalName().equals(dataSourceDto.getDriverClassName())) {
dataSource.setValidationQuery("SELECT 1");
}
else if(com.ibm.db2.jcc.DB2Driver.class.getCanonicalName().equals(dataSourceDto.getDriverClassName())) {
dataSource.setValidationQuery("SELECT 1 FROM SYSIBM.SYSDUMMY1");
}
dataSource.setTestOnBorrow(false);
dataSource.setTestOnReturn(false);
dataSource.setTestOnCreate(true);
dataSource.setTestWhileIdle(true);
dataSource.setInitialSize(3);
dataSource.setMinIdle(3);
dataSource.setMaxIdle(30);
dataSource.setMaxTotal(30);
dynamicRoutingDataSource.addDataSource(dataSourceId, dataSource);
}
return dynamicRoutingDataSource;
}
}

@ -1,57 +0,0 @@
package com.bsmlab.dfx.agent.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan(basePackages = "com.bsmlab.dfx.agent", sqlSessionTemplateRef = "dynamicSqlSessionTemplate")
public class DynamicDataSourceConfig {
@Bean
public DynamicRoutingDataSource dynamicRoutingDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
// 기본 데이터소스 등록
DataSource defaultDataSource = createDataSource("jdbc:mysql://localhost:3306/default_db", "root", "password");
dynamicRoutingDataSource.addDataSource("default", defaultDataSource);
return dynamicRoutingDataSource;
}
@Bean
public DataSourceTransactionManager transactionManager(DynamicRoutingDataSource dynamicRoutingDataSource) {
return new DataSourceTransactionManager(dynamicRoutingDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicRoutingDataSource dynamicRoutingDataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dynamicRoutingDataSource);
return sessionFactory.getObject();
}
@Bean
public SqlSessionTemplate dynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
private DataSource createDataSource(String url, String username, String password) {
// DBCP2 Connection Pool 사용
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

@ -1,28 +0,0 @@
package com.bsmlab.dfx.agent.config;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
@Service
public class DynamicDataSourceService {
@Autowired
private DynamicRoutingDataSource dynamicRoutingDataSource;
public void addNewDataSource(String dataSourceName, String driver, String url, String username, String password) {
DataSource newDataSource = createDataSource(driver, url, username, password);
dynamicRoutingDataSource.addDataSource(dataSourceName, newDataSource);
System.out.println("✅ 새로운 데이터소스 추가됨: " + dataSourceName);
}
private DataSource createDataSource(String driver, String url, String username, String password) {
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

@ -20,7 +20,7 @@ import java.util.Map;
@Slf4j
@Component
public class Settings {
private Map<String, DataSourceDto> dataSourceDtoMap = new HashMap<>();
private final Map<String, DataSourceDto> dataSourceDtoMap = new HashMap<>();
private Resource[] mapperLocations;
public void loadSettingFile(String settingFilePath) {
try {
@ -66,4 +66,8 @@ public class Settings {
public Resource[] getMapperLocations() {
return this.mapperLocations;
}
public Map<String, DataSourceDto> getDataSourceDtoMap() {
return this.dataSourceDtoMap;
}
}

@ -1,12 +0,0 @@
package com.bsmlab.dfx.agent.config;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users")
List<Map<String, Object>> findAll();
}

@ -1,26 +0,0 @@
package com.bsmlab.dfx.agent.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UserService {
private final DynamicRoutingDataSource dynamicRoutingDataSource;
private UserMapper userMapper;
public void fetchUsersFromDefault() {
dynamicRoutingDataSource.setDataSource("default");
System.out.println("🔹 Default DB에서 데이터 조회");
System.out.println(userMapper.findAll());
}
@Transactional
public void fetchUsersFromNewDB() {
dynamicRoutingDataSource.setDataSource("newDB");
System.out.println("🔹 New DB에서 데이터 조회");
System.out.println(userMapper.findAll());
}
}

@ -6,4 +6,5 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DataSourcePool {
}

@ -0,0 +1,32 @@
package com.bsmlab.dfx.agent.config.datasource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
@Slf4j
@Service
public class DynamicDataSourceService {
private final DynamicRoutingDataSource dynamicRoutingDataSource;
private final RefreshableSqlSessionFactoryBean refreshableSqlSessionFactoryBean;
private final Map<String, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
private final Map<String, DataSourceTransactionManager> transactionManagerMap = new HashMap<>();
public SqlSessionFactory getSqlSessionFactory(String dataSourceId) {
if(!this.sqlSessionFactoryMap.containsKey(dataSourceId)) {
DataSource dataSource = this.dynamicRoutingDataSource.getDataSourceMap().get(dataSourceId);
refreshableSqlSessionFactoryBean.setDataSource(dataSource);
this.sqlSessionFactoryMap.put(dataSourceId, refreshableSqlSessionFactoryBean.getObject());
}
return this.sqlSessionFactoryMap.get(dataSourceId);
}
}

@ -1,14 +1,15 @@
package com.bsmlab.dfx.agent.config;
package com.bsmlab.dfx.agent.config.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
private final Map<Object, Object> dataSourceMap = new ConcurrentHashMap<>();
private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
@Override
protected Object determineCurrentLookupKey() {
@ -23,13 +24,17 @@ public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
contextHolder.remove();
}
public void addDataSource(String name, DataSource dataSource) {
dataSourceMap.put(name, dataSource);
super.setTargetDataSources(dataSourceMap);
super.afterPropertiesSet();
public Map<String, DataSource> getDataSourceMap() {
return this.dataSourceMap;
}
public Map<Object, Object> getDataSourceMap() {
return dataSourceMap;
public void addDataSource(String dataSourceId, DataSource dataSource) {
this.dataSourceMap.put(dataSourceId, dataSource);
Map<Object, Object> castingMap = new ConcurrentHashMap<>();
for(Object key : this.dataSourceMap.keySet()) {
castingMap.put(key, this.dataSourceMap.get(key));
}
super.setTargetDataSources(castingMap);
super.afterPropertiesSet();
}
}

@ -4,7 +4,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
@ -18,7 +17,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
@Component
@DependsOn(value = {"settings"})
public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean {
private SqlSessionFactory proxy;
private int interval = 500;

@ -0,0 +1,79 @@
package com.bsmlab.dfx.agent.config.datasource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
@Service
@Slf4j
public class SqlExecuteService {
private final DynamicRoutingDataSource dynamicRoutingDataSource;
private final DynamicDataSourceService dynamicDataSourceService;
public List<Map<String, Object>> select(String dataSourceId, String sqlId, Map<String, Object> parameter) {
dynamicRoutingDataSource.setDataSource(dataSourceId);
try {
SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId);
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession.selectList(sqlId, parameter);
}
finally {
dynamicRoutingDataSource.clearDataSource();
}
}
public Map<String, Object> insert(String dataSourceId, String sqlId, Map<String, Object> parameter) {
dynamicRoutingDataSource.setDataSource(dataSourceId);
try {
SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert(sqlId, parameter);
return parameter;
}
finally {
dynamicRoutingDataSource.clearDataSource();
}
}
public int update(String dataSourceId, String sqlId, Map<String, Object> parameter) {
dynamicRoutingDataSource.setDataSource(dataSourceId);
try {
SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId);
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession.update(sqlId, parameter);
}
finally {
dynamicRoutingDataSource.clearDataSource();
}
}
public int delete(String dataSourceId, String sqlId, Map<String, Object> parameter) {
dynamicRoutingDataSource.setDataSource(dataSourceId);
try {
SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId);
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession.delete(sqlId, parameter);
}
finally {
dynamicRoutingDataSource.clearDataSource();
}
}
public void execute(String dataSourceId, String sqlId, Map<String, Object> parameter) {
dynamicRoutingDataSource.setDataSource(dataSourceId);
try {
SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.selectOne(sqlId, parameter);
}
finally {
dynamicRoutingDataSource.clearDataSource();
}
}
}
Loading…
Cancel
Save