244 lines
11 KiB
Java
244 lines
11 KiB
Java
package ru.nbch.indicsearchservice.util;
|
|
|
|
import com.zaxxer.hikari.HikariConfig;
|
|
import com.zaxxer.hikari.HikariDataSource;
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDateTime;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Supplier;
|
|
import javax.sql.DataSource;
|
|
import org.apache.ibatis.session.Configuration;
|
|
import org.apache.ibatis.session.SqlSessionFactory;
|
|
import org.apache.ibatis.type.JdbcType;
|
|
import org.mybatis.spring.SqlSessionFactoryBean;
|
|
import org.mybatis.spring.SqlSessionTemplate;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.context.event.EventListener;
|
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|
import org.springframework.stereotype.Component;
|
|
import ru.nbch.indicsearchservice.config.settings.ApplicationSettings;
|
|
import ru.nbch.indicsearchservice.config.settings.DataSourceSettings;
|
|
import ru.nbch.indicsearchservice.event.DataSourceChangedEvent;
|
|
import ru.nbch.indicsearchservice.exception.InternalException;
|
|
import ru.nbch.indicsearchservice.repository.MyBatisRepository;
|
|
import ru.nbch.indicsearchservice.repository.indic.IndicMapper;
|
|
import ru.nbch.indicsearchservice.service.SettingsService;
|
|
import ru.nbch.indicsearchservice.util.typehandler.NullableLocalDateTimeTypeHandler;
|
|
import ru.nbch.indicsearchservice.util.typehandler.NullableLocalDateTypeHandler;
|
|
|
|
@Component
|
|
public class DataSourceUpdater {
|
|
private static final Logger log = LoggerFactory.getLogger(DataSourceUpdater.class);
|
|
|
|
private final ApplicationSettings settings;
|
|
private final Map<DataSourceType, DataSourceData> dataSourceMap = new ConcurrentHashMap<>();
|
|
|
|
public DataSourceUpdater(ApplicationSettings settings, SettingsService settingsService) {
|
|
this.settings = settings;
|
|
|
|
buildSettings(DataSourceType.INDIC, settingsService, settings::setIndicDataSource);
|
|
|
|
dataSourceMap.put(
|
|
DataSourceType.INDIC,
|
|
new DataSourceData(new AtomicReference<>(null), new AtomicReference<>(null), new AtomicReference<>(null))
|
|
);
|
|
}
|
|
|
|
@EventListener
|
|
public void onDatabaseChanged(DataSourceChangedEvent event) {
|
|
log.info("Connection to datasource {} invalidated", event.dataSourceType());
|
|
|
|
if (!dataSourceMap.containsKey(event.dataSourceType())) {
|
|
log.error("Unknown datasource type {}", event.dataSourceType());
|
|
return;
|
|
}
|
|
|
|
DataSource oldDataSource = dataSourceMap.get(event.dataSourceType()).dataSourceRef().getAndSet(null);
|
|
if (oldDataSource instanceof HikariDataSource ds) {
|
|
ds.close();
|
|
}
|
|
|
|
dataSourceMap.get(event.dataSourceType()).sqlSessionFactoryRef().set(null);
|
|
dataSourceMap.get(event.dataSourceType()).mapperRef().set(null);
|
|
}
|
|
|
|
public DataSource getDataSource(DataSourceType dataSourceType) {
|
|
if (!dataSourceMap.containsKey(dataSourceType)) {
|
|
log.error("Unknown datasource type {}", dataSourceType);
|
|
return null;
|
|
}
|
|
|
|
return getComponent(dataSourceMap.get(dataSourceType).dataSourceRef(), () -> buildDataSource(dataSourceType), "DataSource");
|
|
}
|
|
|
|
public SqlSessionFactory getSqlSessionFactory(DataSourceType dataSourceType) {
|
|
if (!dataSourceMap.containsKey(dataSourceType)) {
|
|
log.error("Unknown datasource type {}", dataSourceType);
|
|
return null;
|
|
}
|
|
|
|
return getComponent(dataSourceMap.get(dataSourceType).sqlSessionFactoryRef(), () -> buildSqlSessionFactory(dataSourceType), "SqlSessionFactory");
|
|
}
|
|
|
|
public MyBatisRepository getMapper(DataSourceType dataSourceType) {
|
|
if (!dataSourceMap.containsKey(dataSourceType)) {
|
|
log.error("Unknown datasource type {}", dataSourceType);
|
|
return null;
|
|
}
|
|
|
|
return getComponent(dataSourceMap.get(dataSourceType).mapperRef(), () -> buildMapper(dataSourceType), "Mapper");
|
|
}
|
|
|
|
private <T> T getComponent(AtomicReference<T> ref, Supplier<T> builder, String componentName) {
|
|
T component = ref.get();
|
|
if (component == null) {
|
|
synchronized (ref) {
|
|
component = ref.get();
|
|
if (component == null) {
|
|
try {
|
|
component = builder.get();
|
|
ref.set(component);
|
|
log.info("{} created successfully", componentName);
|
|
} catch (Exception e) {
|
|
log.error("Failed to build {}", componentName, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return component;
|
|
}
|
|
|
|
private DataSource buildDataSource(DataSourceType dataSourceType) {
|
|
DataSourceSettings dataSourceSettings = switch (dataSourceType) {
|
|
case INDIC -> settings.getIndicDataSource();
|
|
};
|
|
|
|
if (dataSourceSettings == null) {
|
|
log.error("{} configuration is null", dataSourceType);
|
|
return null;
|
|
}
|
|
|
|
HikariConfig hikariConfig = new HikariConfig();
|
|
hikariConfig.setDriverClassName(dataSourceSettings.getDriver());
|
|
hikariConfig.setJdbcUrl(dataSourceSettings.getUrl());
|
|
hikariConfig.setUsername(dataSourceSettings.getUsername());
|
|
hikariConfig.setPassword(dataSourceSettings.getPassword());
|
|
|
|
hikariConfig.setConnectionTimeout(dataSourceSettings.getConnectionTimeout());
|
|
hikariConfig.setValidationTimeout(dataSourceSettings.getValidationTimeout());
|
|
hikariConfig.setIdleTimeout(dataSourceSettings.getIdleTimeout());
|
|
hikariConfig.setMaximumPoolSize(dataSourceSettings.getMaxPoolSize());
|
|
hikariConfig.setMaxLifetime(dataSourceSettings.getMaxLifetime());
|
|
hikariConfig.setKeepaliveTime(dataSourceSettings.getKeepAliveTime());
|
|
|
|
hikariConfig.setPoolName(dataSourceType + "-pool");
|
|
|
|
if (Objects.equals(dataSourceSettings.getDriver(), "net.sourceforge.jtds.jdbc.Driver")) {
|
|
hikariConfig.setConnectionTestQuery("select 1");
|
|
}
|
|
|
|
return new HikariDataSource(hikariConfig);
|
|
}
|
|
|
|
private SqlSessionFactory buildSqlSessionFactory(DataSourceType dataSourceType) {
|
|
DataSource dataSource = getDataSource(dataSourceType);
|
|
try {
|
|
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
|
|
sqlSessionFactoryBean.setDataSource(dataSource);
|
|
|
|
Configuration configuration = new Configuration();
|
|
configuration.setMapUnderscoreToCamelCase(true);
|
|
configuration.setCallSettersOnNulls(true);
|
|
configuration.getTypeHandlerRegistry()
|
|
.register(LocalDate.class, JdbcType.DATE, new NullableLocalDateTypeHandler());
|
|
configuration.getTypeHandlerRegistry()
|
|
.register(LocalDateTime.class, JdbcType.TIMESTAMP, new NullableLocalDateTimeTypeHandler());
|
|
|
|
sqlSessionFactoryBean.setConfiguration(configuration);
|
|
|
|
if (dataSourceType == DataSourceType.INDIC) {
|
|
sqlSessionFactoryBean.setMapperLocations(
|
|
new PathMatchingResourcePatternResolver()
|
|
.getResources("classpath:mappers/indic/*.xml")
|
|
);
|
|
} else {
|
|
Class<?> mapperClass = getMapperClass(dataSourceType);
|
|
configuration.addMapper(mapperClass);
|
|
}
|
|
|
|
SqlSessionFactory sessionFactory = sqlSessionFactoryBean.getObject();
|
|
if (sessionFactory == null) {
|
|
throw new InternalException("Failed to create SqlSessionFactory");
|
|
}
|
|
|
|
return sessionFactory;
|
|
} catch (Exception e) {
|
|
log.error("Failed to build SqlSessionFactory", e);
|
|
throw new InternalException("Failed to build SqlSessionFactory " + e);
|
|
}
|
|
}
|
|
|
|
private <T extends MyBatisRepository> T buildMapper(DataSourceType dataSourceType) {
|
|
Class<?> mapperClass = getMapperClass(dataSourceType);
|
|
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(dataSourceType);
|
|
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
|
|
return (T) sqlSessionTemplate.getMapper(mapperClass);
|
|
}
|
|
|
|
private Class<?> getMapperClass(DataSourceType dataSourceType) {
|
|
return switch (dataSourceType) {
|
|
case INDIC -> IndicMapper.class;
|
|
};
|
|
}
|
|
|
|
private void buildSettings(DataSourceType dataSourceType, SettingsService settingsService, Consumer<DataSourceSettings> settingsConsumer) {
|
|
String key = switch (dataSourceType) {
|
|
case INDIC -> "indic";
|
|
};
|
|
|
|
Map<String, Optional<String>> fetchedSettings = settingsService.getSettingsValuesByNames(List.of(
|
|
"datasource." + key + ".driver",
|
|
"datasource." + key + ".url",
|
|
"datasource." + key + ".username",
|
|
"datasource." + key + ".password",
|
|
"datasource." + key + ".connection-timeout",
|
|
"datasource." + key + ".validation-timeout",
|
|
"datasource." + key + ".idle-timeout",
|
|
"datasource." + key + ".max-pool-size",
|
|
"datasource." + key + ".max-lifetime",
|
|
"datasource." + key + ".keep-alive-time"
|
|
));
|
|
|
|
DataSourceSettings dataSourceSettings = new DataSourceSettings();
|
|
dataSourceSettings.setDriver(fetchedSettings.get("datasource." + key + ".driver").orElseThrow(() -> new InternalException("Key datasource." + key + ".driver" + " was not found in SETTINGS!")));
|
|
dataSourceSettings.setUrl(fetchedSettings.get("datasource." + key + ".url").orElseThrow(() -> new InternalException("Key datasource." + key + ".url" + " was not found in SETTINGS!")));
|
|
dataSourceSettings.setUsername(fetchedSettings.get("datasource." + key + ".username").orElseThrow(() -> new InternalException("Key datasource." + key + ".username" + " was not found in SETTINGS!")));
|
|
dataSourceSettings.setPassword(fetchedSettings.get("datasource." + key + ".password").orElseThrow(() -> new InternalException("Key datasource." + key + ".password" + " was not found in SETTINGS!")));
|
|
|
|
dataSourceSettings.setConnectionTimeout(Long.valueOf(fetchedSettings.get("datasource." + key + ".connection-timeout").orElse("30000")));
|
|
dataSourceSettings.setValidationTimeout(Long.valueOf(fetchedSettings.get("datasource." + key + ".validation-timeout").orElse("5000")));
|
|
dataSourceSettings.setIdleTimeout(Long.valueOf(fetchedSettings.get("datasource." + key + ".idle-timeout").orElse("600000")));
|
|
dataSourceSettings.setMaxPoolSize(Integer.valueOf(fetchedSettings.get("datasource." + key + ".max-pool-size").orElse("100")));
|
|
dataSourceSettings.setMaxLifetime(Long.valueOf(fetchedSettings.get("datasource." + key + ".max-lifetime").orElse("1800000")));
|
|
dataSourceSettings.setKeepAliveTime(Long.valueOf(fetchedSettings.get("datasource." + key + ".keep-alive-time").orElse("120000")));
|
|
|
|
settingsConsumer.accept(dataSourceSettings);
|
|
}
|
|
|
|
private record DataSourceData(AtomicReference<DataSource> dataSourceRef,
|
|
AtomicReference<SqlSessionFactory> sqlSessionFactoryRef,
|
|
AtomicReference<MyBatisRepository> mapperRef) {
|
|
}
|
|
|
|
public enum DataSourceType {
|
|
INDIC
|
|
}
|
|
}
|