indic-search-service/src/main/java/ru/nbch/indicsearchservice/util/DataSourceUpdater.java
2026-05-28 12:08:58 +03:00

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
}
}