diff --git a/src/main/java/com/bsmlab/dfx/agent/config/DfxAgentConfiguration.java b/src/main/java/com/bsmlab/dfx/agent/config/DfxAgentConfiguration.java index 37d4fb3..168ea14 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/DfxAgentConfiguration.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/DfxAgentConfiguration.java @@ -1,20 +1,34 @@ package com.bsmlab.dfx.agent.config; import com.bsmlab.dfx.agent.config.datasource.DataSourceDto; +import com.bsmlab.dfx.agent.config.datasource.DynamicDataSourceService; 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.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScans; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import javax.sql.DataSource; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; import java.util.Map; @Slf4j @@ -24,18 +38,23 @@ import java.util.Map; @MapperScan("com.bsmlab.dfx.agent") }) public class DfxAgentConfiguration { + private static final String DB_SOURCE_FILE_PATH = "./storages/dfxagent.mv.db"; + // gradle bootRun 실행 설정 // bootRun --args="--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" // command line java 실행 설정 // 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 + //example: embedded.db.file.directory=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples @Value("${embedded.db.file.directory}") private String embeddedDbFileDirectory; - // setting.file=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples\dfxagent.json + //example: setting.file=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples\dfxagent.json @Value("${setting.file}") private String settingFile; + private Map temporarySqlSessionFactoryMap = new HashMap<>(); + Map temporaryTransactionManagerMap = new HashMap<>(); + @Bean("settings") public Settings loadSettings() { if(StringUtils.isBlank(this.embeddedDbFileDirectory)) { @@ -53,17 +72,110 @@ public class DfxAgentConfiguration { return settings; } - @Bean - public SqlSessionFactoryBean sqlSessionFactoryBean(Settings settings) { - RefreshableSqlSessionFactoryBean sqlSessionFactoryBean = new RefreshableSqlSessionFactoryBean(); - Resource[] mapperLocations = settings.getMapperLocations(); - sqlSessionFactoryBean.setMapperLocations(mapperLocations); - return sqlSessionFactoryBean; - } - @Bean(name = "dynamicRoutingDataSource") public DynamicRoutingDataSource dynamicRoutingDataSource(Settings settings) { DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); + Map sqlSessionFactoryMap = new HashMap<>(); + Map transactionManagerMap = new HashMap<>(); + + Map 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); + SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); + sqlSessionFactoryBean.setDataSource(dataSource); + try { + sqlSessionFactoryMap.put(dataSourceId, sqlSessionFactoryBean.getObject()); + } catch (Exception e) { + log.error("DynamicRoutingDataSource 생성 중 오류: {}", e.getMessage(), e); + throw new RuntimeException(e); + } + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + transactionManagerMap.put(dataSourceId, transactionManager); + dynamicRoutingDataSource.addDataSource(dataSourceId, dataSource); + } + this.temporarySqlSessionFactoryMap = sqlSessionFactoryMap; + this.temporaryTransactionManagerMap = transactionManagerMap; return dynamicRoutingDataSource; } + + @Bean(name = "dynamicDataSourceService") + public DynamicDataSourceService dynamicDataSourceService() { + DynamicDataSourceService dynamicDataSourceService = new DynamicDataSourceService(); + dynamicDataSourceService.setSqlSessionFactoryMap(this.temporarySqlSessionFactoryMap); + dynamicDataSourceService.setTransactionManagerMap(this.temporaryTransactionManagerMap); + return dynamicDataSourceService; + } + + public void copyEmbeddedDbFileIfNotExists() { + String embeddedDbFileDirectory = System.getProperty("embedded.db.file.directory"); + File targetDirectory = new File(embeddedDbFileDirectory); + if(!targetDirectory.exists()) { + targetDirectory = new File(System.getProperty("user.home")); + } + File targetFile = new File(targetDirectory.getAbsoluteFile() + "/dfxagent.mv.db"); + log.debug("embeddedDbFilePath: {}", embeddedDbFileDirectory); + if(!targetFile.exists()) { + try { + InputStream inputStream = new ClassPathResource(DB_SOURCE_FILE_PATH).getInputStream(); + log.debug("embedded db file copy from {} to {}", DB_SOURCE_FILE_PATH, embeddedDbFileDirectory); + Files.copy(inputStream, Paths.get(targetFile.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING); + } + catch (IOException e) { + log.error("cannot make Embedded DB files. exit application."); + System.exit(0); + } + } + } + + @Bean(name = "innerDataSource") + @ConfigurationProperties("spring.datasource") // application.yml 에서 설정된 값 자동 적용 + public DataSource innerDataSource(DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + @Bean(name = "innerSqlSessionFactory") + public SqlSessionFactory innerSqlSessionFactory(@Qualifier("innerDataSource") DataSource dataSource, @Qualifier("settings") Settings settings) throws Exception { + SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); + sqlSessionFactoryBean.setDataSource(dataSource); + sqlSessionFactoryBean.setMapperLocations(settings.getMapperLocations()); + return sqlSessionFactoryBean.getObject(); + } + + @Bean(name = "innerSqlSessionTemplate") + public SqlSessionTemplate innerSqlSessionTemplate(@Qualifier("innerSqlSessionFactory") SqlSessionFactory innerSqlSessionFactory) { + return new SqlSessionTemplate(innerSqlSessionFactory); + } + + @Bean(name = "innerTransactionManager") + public DataSourceTransactionManager innerTransactionManager(@Qualifier("innerDataSource") DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } } diff --git a/src/main/java/com/bsmlab/dfx/agent/config/EmbeddedDbInitializer.java b/src/main/java/com/bsmlab/dfx/agent/config/EmbeddedDbConfiguration.java similarity index 74% rename from src/main/java/com/bsmlab/dfx/agent/config/EmbeddedDbInitializer.java rename to src/main/java/com/bsmlab/dfx/agent/config/EmbeddedDbConfiguration.java index a46ce8b..d9da871 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/EmbeddedDbInitializer.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/EmbeddedDbConfiguration.java @@ -1,6 +1,8 @@ package com.bsmlab.dfx.agent.config; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; @@ -12,10 +14,17 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; @Slf4j -@Component -public class EmbeddedDbInitializer { +@Configuration +public class EmbeddedDbConfiguration { private static final String DB_SOURCE_FILE_PATH = "./storages/dfxagent.mv.db"; + //example: embedded.db.file.directory=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples + @Value("${embedded.db.file.directory}") + private String embeddedDbFileDirectory; + //example: setting.file=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples\dfxagent.json + @Value("${setting.file}") + private String settingFile; + public void copyEmbeddedDbFileIfNotExists() { log.debug("run"); diff --git a/src/main/java/com/bsmlab/dfx/agent/config/Settings.java b/src/main/java/com/bsmlab/dfx/agent/config/Settings.java index 7a9f78b..1f0ba76 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/Settings.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/Settings.java @@ -58,6 +58,7 @@ public class Settings { int i = 0; for(String location : locationStringList) { resources[i] = new InputStreamResource(new FileInputStream(new File(location))); + i++; } } return resources; diff --git a/src/main/java/com/bsmlab/dfx/agent/config/StartupRunner.java b/src/main/java/com/bsmlab/dfx/agent/config/StartupRunner.java index 8483e2e..db20b32 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/StartupRunner.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/StartupRunner.java @@ -10,7 +10,7 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor @Component public class StartupRunner implements ApplicationRunner { - private final EmbeddedDbInitializer embeddedDbInitializer; + private final EmbeddedDbConfiguration embeddedDbConfiguration; @Override public void run(ApplicationArguments args) throws Exception { diff --git a/src/main/java/com/bsmlab/dfx/agent/config/datasource/DataSourcePool.java b/src/main/java/com/bsmlab/dfx/agent/config/datasource/DataSourcePool.java deleted file mode 100644 index 8177e24..0000000 --- a/src/main/java/com/bsmlab/dfx/agent/config/datasource/DataSourcePool.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bsmlab.dfx.agent.config.datasource; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class DataSourcePool { - -} diff --git a/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicDataSourceService.java b/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicDataSourceService.java index 59dfe4d..628cbcd 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicDataSourceService.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicDataSourceService.java @@ -1,80 +1,32 @@ package com.bsmlab.dfx.agent.config.datasource; -import com.bsmlab.dfx.agent.config.Settings; -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.dbcp2.BasicDataSource; -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.context.annotation.DependsOn; import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.stereotype.Service; -import org.springframework.transaction.TransactionManager; +import org.springframework.stereotype.Component; -import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; -@RequiredArgsConstructor @Slf4j -@Service -@DependsOn({"settings", "dynamicRoutingDataSource"}) +@Component public class DynamicDataSourceService { - private final Settings settings; - private final DynamicRoutingDataSource dynamicRoutingDataSource; - private final RefreshableSqlSessionFactoryBean refreshableSqlSessionFactoryBean; - private final Map sqlSessionFactoryMap = new HashMap<>(); - private final Map transactionManagerMap = new HashMap<>(); + private Map sqlSessionFactoryMap = new HashMap<>(); + private Map transactionManagerMap = new HashMap<>(); public SqlSessionFactory getSqlSessionFactory(String dataSourceId) { return this.sqlSessionFactoryMap.get(dataSourceId); } - public TransactionManager getTransactionManager(String dataSourceId) { + public DataSourceTransactionManager getTransactionManager(String dataSourceId) { return this.transactionManagerMap.get(dataSourceId); } - @PostConstruct - public void init() { - Map 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); - refreshableSqlSessionFactoryBean.setDataSource(dataSource); - sqlSessionFactoryMap.put(dataSourceId, refreshableSqlSessionFactoryBean.getObject()); - DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); - transactionManagerMap.put(dataSourceId, transactionManager); - } + public void setSqlSessionFactoryMap(Map sqlSessionFactoryMap) { + this.sqlSessionFactoryMap = sqlSessionFactoryMap; + } + + public void setTransactionManagerMap(Map transactionManagerMap) { + this.transactionManagerMap = transactionManagerMap; } } diff --git a/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicRoutingDataSource.java b/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicRoutingDataSource.java index 89730d2..d566049 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicRoutingDataSource.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/datasource/DynamicRoutingDataSource.java @@ -1,7 +1,6 @@ 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; diff --git a/src/main/java/com/bsmlab/dfx/agent/config/datasource/RefreshableSqlSessionFactoryBean.java b/src/main/java/com/bsmlab/dfx/agent/config/datasource/RefreshableSqlSessionFactoryBean.java index b2eb3f5..661d154 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/datasource/RefreshableSqlSessionFactoryBean.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/datasource/RefreshableSqlSessionFactoryBean.java @@ -5,7 +5,6 @@ import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.beans.factory.DisposableBean; import org.springframework.core.io.Resource; -import org.springframework.stereotype.Component; import java.io.IOException; import java.lang.reflect.InvocationHandler; @@ -16,7 +15,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; @Slf4j -@Component public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean { private SqlSessionFactory proxy; private int interval = 500; @@ -40,12 +38,16 @@ public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean impl public void refresh() throws Exception { if(log.isInfoEnabled()) { - log.info("> Refresh SqlMapper"); - log.info("======================================================================================"); + log.info("> Refreshing SQL Mapper Configuration..."); } w.lock(); try { super.afterPropertiesSet(); + // refresh()가 호출될 때 기존 proxy를 재생성하여 기존 세션과 충돌을 방지한다. + proxy = (SqlSessionFactory) Proxy.newProxyInstance( + SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSessionFactory.class} + , (proxy, method, args) -> method.invoke(getParentObject(), args) + ); } finally { w.unlock(); } diff --git a/src/main/java/com/bsmlab/dfx/agent/config/datasource/SqlExecuteService.java b/src/main/java/com/bsmlab/dfx/agent/config/datasource/SqlExecuteService.java index b898645..abfc71a 100644 --- a/src/main/java/com/bsmlab/dfx/agent/config/datasource/SqlExecuteService.java +++ b/src/main/java/com/bsmlab/dfx/agent/config/datasource/SqlExecuteService.java @@ -18,9 +18,7 @@ public class SqlExecuteService { public List> select(String dataSourceId, String sqlId, Map parameter) { dynamicRoutingDataSource.setDataSource(dataSourceId); - try { - SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId); - SqlSession sqlSession = sqlSessionFactory.openSession(); + try(SqlSession sqlSession = dynamicDataSourceService.getSqlSessionFactory(dataSourceId).openSession()) { return sqlSession.selectList(sqlId, parameter); } finally { @@ -30,9 +28,7 @@ public class SqlExecuteService { public Map insert(String dataSourceId, String sqlId, Map parameter) { dynamicRoutingDataSource.setDataSource(dataSourceId); - try { - SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId); - SqlSession sqlSession = sqlSessionFactory.openSession(); + try(SqlSession sqlSession = dynamicDataSourceService.getSqlSessionFactory(dataSourceId).openSession()) { sqlSession.insert(sqlId, parameter); return parameter; } @@ -43,9 +39,7 @@ public class SqlExecuteService { public int update(String dataSourceId, String sqlId, Map parameter) { dynamicRoutingDataSource.setDataSource(dataSourceId); - try { - SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId); - SqlSession sqlSession = sqlSessionFactory.openSession(); + try(SqlSession sqlSession = dynamicDataSourceService.getSqlSessionFactory(dataSourceId).openSession()) { return sqlSession.update(sqlId, parameter); } finally { @@ -55,9 +49,7 @@ public class SqlExecuteService { public int delete(String dataSourceId, String sqlId, Map parameter) { dynamicRoutingDataSource.setDataSource(dataSourceId); - try { - SqlSessionFactory sqlSessionFactory = dynamicDataSourceService.getSqlSessionFactory(dataSourceId); - SqlSession sqlSession = sqlSessionFactory.openSession(); + try(SqlSession sqlSession = dynamicDataSourceService.getSqlSessionFactory(dataSourceId).openSession()) { return sqlSession.delete(sqlId, parameter); } finally {