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

main
semin.baek 11 months ago
parent c01e2d3c36
commit 8a56c9a3af

@ -37,6 +37,7 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0'
} }
tasks.named('test') { tasks.named('test') {

@ -1 +1 @@
rootProject.name = 'agent' rootProject.name = 'dfxagent'

@ -0,0 +1,19 @@
{
"datasource": [
{
"dataSourceId": "dfcms",
"driverClassName": "org.postgresql.Driver",
"url": "jdbc:postgresql://bsm-lab.com:5432/defree?currentSchema=DFCMS",
"username": "defreeadmin",
"password": "qortpals1!"
},
{
"dataSourceId": "mochastory",
"driverClassName": "com.mysql.jdbc.Driver",
"url": "jdbc:mysql://bsm-lab.com:3306/MOCHASTORY?allowPublicKeyRetrieval=true",
"username": "MOCHASTORY",
"password": "MOCHASTORY"
}
],
"sqlMapperLocations": ["D:/projects/bsm-lab/dfx/dfxagent/src/docs/mapper-examples/**/*.xml"]
}

@ -0,0 +1,12 @@
datasource:
dfcms:
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://bsm-lab.com:5432/defree?currentSchema=DFCMS
username: defreeadmin
password: qortpals1!
mochastory:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://bsm-lab.com:3306/MOCHASTORY?allowPublicKeyRetrieval=true
username: MOCHASTORY
password: MOCHASTORY

@ -1,37 +0,0 @@
package com.bsmlab.dfx.agent.config;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.Map;
@Slf4j
@Component
public class DfxAgengConfiguration {
private String settingFilePath;
public void loadConfiguraion() {
//setting.file=D:\projects\bsm-lab\dfx\run\dfxagent.json
String embeddedDbFileDirectory = System.getProperty("embedded.db.file.directory");
this.settingFilePath = System.getProperty("setting.file");
try {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> settingMap = mapper.readValue(new File(this.settingFilePath), Map.class);
} catch (DatabindException e) {
log.error("cannot parse a setting file. {}", this.settingFilePath, e);
} catch (IOException e) {
log.error("cannot found a setting file. {}", this.settingFilePath);
log.error("-Dsetting.file=[file path] needed.");
}
}
public String getSettingFilePath() {
return this.settingFilePath;
}
}

@ -0,0 +1,54 @@
package com.bsmlab.dfx.agent.config;
import com.bsmlab.dfx.agent.config.datasource.RefreshableSqlSessionFactoryBean;
import io.micrometer.common.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.SqlSessionFactoryBean;
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;
@Slf4j
@Configuration
@RequiredArgsConstructor
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
@Value("${embedded.db.file.directory}")
private String embeddedDbFileDirectory;
// setting.file=D:\projects\bsm-lab\dfx\dfxagent\src\docs\settings-examples\dfxagent.json
@Value("${setting.file}")
private String settingFile;
@Bean("settings")
public Settings loadSettings() {
if(StringUtils.isBlank(this.embeddedDbFileDirectory)) {
log.error("cannot found a embedded DB file. {}", this.embeddedDbFileDirectory);
log.error("exit application");
System.exit(0);
}
if(StringUtils.isBlank(this.settingFile)) {
log.error("cannot found a setting file. {}", this.settingFile);
log.error("exit application");
System.exit(0);
}
Settings settings = new Settings();
settings.loadSettingFile(this.settingFile);
return settings;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean () {
RefreshableSqlSessionFactoryBean sqlSessionFactoryBean = new RefreshableSqlSessionFactoryBean();
//setMapperLocations(Resource[] mapperLocations)
Settings settings = applicationContext.getBean(Settings.class);
Resource[] mapperLocations = settings.getMapperLocations();
sqlSessionFactoryBean.setMapperLocations(mapperLocations);
return sqlSessionFactoryBean;
}
}

@ -0,0 +1,69 @@
package com.bsmlab.dfx.agent.config;
import com.bsmlab.dfx.agent.config.datasource.DataSourceDto;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Component
public class Settings {
private Map<String, DataSourceDto> dataSourceDtoMap = new HashMap<>();
private Resource[] mapperLocations;
public void loadSettingFile(String settingFilePath) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> settingMap = objectMapper.readValue(new File(settingFilePath), Map.class);
this.mapperLocations = this.createMapperLocations(settingMap);
log.debug("settingMap: {}", settingMap);
this.parseDataSources(settingMap);
} catch (DatabindException e) {
log.error("cannot parse a setting file. {}", settingFilePath, e);
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error("cannot read a setting file. {}", settingFilePath);
log.error(e.getMessage(), e);
}
}
@SuppressWarnings("unchecked")
private void parseDataSources(Map<String, Object> settingMap) {
List<Map<String, String>> dataSourceMapList = (List<Map<String, String>>)settingMap.get("datasource");
for(Map<String, String> dataSourceMap : dataSourceMapList) {
DataSourceDto dataSourceDto = DataSourceDto.builder()
.dataSourceId(dataSourceMap.get("dataSourceId")).driverClassName(dataSourceMap.get("driverClassName"))
.url(dataSourceMap.get("url")).username(dataSourceMap.get("username")).password(dataSourceMap.get("password"))
.build();
dataSourceDtoMap.put(dataSourceDto.getDataSourceId(), dataSourceDto);
}
}
private Resource[] createMapperLocations(Map<String, Object> settingMap) throws FileNotFoundException {
Resource[] resources = null;
if(ObjectUtils.isNotEmpty(settingMap.get("mapperLocations"))) {
String[] locationStringList = (String[])settingMap.get("mapperLocations");
resources = new Resource[locationStringList.length];
int i = 0;
for(String location : locationStringList) {
resources[i] = new InputStreamResource(new FileInputStream(new File(location)));
}
}
return resources;
}
public Resource[] getMapperLocations() {
return this.mapperLocations;
}
}

@ -17,7 +17,7 @@ public class StartupRunner implements ApplicationRunner {
log.debug("run"); log.debug("run");
//TODO 1. dfxagent.yml 로드 및 설정 관리자에 저장 //TODO 1. dfxagent.yml 로드 및 설정 관리자에 저장
//TODO 2. embedded db 체크 및 생성 //TODO 2. embedded db 체크 및 생성
embeddedDbInitializer.copyEmbeddedDbFileIfNotExists(); //embeddedDbInitializer.copyEmbeddedDbFileIfNotExists();
//TODO 3. dfxagent.yml 을 기반으로 데이터소스 및 커넥션 풀 생성 //TODO 3. dfxagent.yml 을 기반으로 데이터소스 및 커넥션 풀 생성
//TODO 4. Worker 쓰레드 생성 //TODO 4. Worker 쓰레드 생성

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

@ -0,0 +1,18 @@
package com.bsmlab.dfx.agent.config.datasource;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DataSourceDto {
private String dataSourceId;
private String driverClassName;
private String url;
private String username;
private String password;
}

@ -0,0 +1,9 @@
package com.bsmlab.dfx.agent.config.datasource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DataSourcePool {
}

@ -0,0 +1,180 @@
package com.bsmlab.dfx.agent.config.datasource;
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;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.locks.Lock;
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;
private Timer timer;
private TimerTask task;
private Resource[] mapperLocations;
private boolean running = false;
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
@Override
public void setMapperLocations(Resource[] mapperLocations) {
super.setMapperLocations(mapperLocations);
this.mapperLocations = mapperLocations;
}
public void setInterval(int interval) {
this.interval = interval;
}
public void refresh() throws Exception {
if(log.isInfoEnabled()) {
log.info("> Refresh SqlMapper");
log.info("======================================================================================");
}
w.lock();
try {
super.afterPropertiesSet();
} finally {
w.unlock();
}
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
setRefreshable();
}
private void setRefreshable() {
proxy = (SqlSessionFactory) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSessionFactory.class},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(getParentObject(), args);
}
});
task = new TimerTask() {
private Map<Resource, Long> map = new HashMap<>();
public void run() {
if(isModified()) {
try {
refresh();
} catch(Exception e) {
log.error("caught exception", e);
}
}
}
private boolean isModified() {
boolean retVal = false;
if(mapperLocations != null) {
for(int i = 0; i < mapperLocations.length; i++) {
Resource mappingLocation = mapperLocations[i];
retVal |= findModifiedResource(mappingLocation);
}
}
return retVal;
}
private boolean findModifiedResource(Resource resource) {
boolean retVal = false;
List<String> modifiedResources = new ArrayList<>();
try {
long modified = resource.lastModified();
if(map.containsKey(resource)) {
long lastModified = ((Long) map.get(resource)) .longValue();
if(lastModified != modified) {
map.put(resource, modified);
//modifiedResources.add(resource.getDescription()); // 전체경로
modifiedResources.add(resource.getFilename()); // 파일명
retVal = true;
}
} else {
map.put(resource, modified);
}
} catch (IOException e) {
log.error("caught exception", e);
}
if(retVal) {
if(log.isInfoEnabled()) {
log.info("======================================================================================");
log.info("> Update File name : " + modifiedResources);
}
}
return retVal;
}
};
timer = new Timer(true);
resetInterval();
}
private Object getParentObject() throws Exception {
r.lock();
try {
return super.getObject();
} finally {
r.unlock();
}
}
@Override
public SqlSessionFactory getObject() {
return this.proxy;
}
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return (this.proxy != null ? this.proxy.getClass() : SqlSessionFactory.class);
}
@Override
public boolean isSingleton() {
return true;
}
public void setCheckInterval(int ms) {
interval = ms;
if(timer != null) {
resetInterval();
}
}
private void resetInterval() {
if(running) {
timer.cancel();
running = false;
}
if(interval > 0) {
timer.schedule(task, 0, interval); running = true;
}
}
@Override
public void destroy() throws Exception {
timer.cancel();
}
}

@ -9,4 +9,7 @@ spring:
url: jdbc:h2:classpath:/storages/dfxagent url: jdbc:h2:classpath:/storages/dfxagent
username: smbaek username: smbaek
password: qortpals1! password: qortpals1!
logging:
level:
com.bsmlab.dfx.agent: DEBUG

@ -1,13 +1,14 @@
package com.bsmlab.dfx.agent; package com.bsmlab.dfx.agent;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest @SpringBootTest
class DfxAgentApplicationTests { class DfxAgentApplicationTests {
// @Autowired
// private DfxAgengConfiguration dfxAgengConfiguration;
@Test // @Test
void contextLoads() { // void contextLoads() {
} // }
} }

Loading…
Cancel
Save