インタフェースを介してスプリングブートプロジェクトは、動的データソースを削除/追加し、データソースを追加し、動的データ・ソースを切り替え、その後mybaties照会を切り替えた後、データソースを使用します。
まず、データベース内のテーブルを作成するために必要なデータソース、データベース内の接続情報テーブルに格納された情報。
#数据源信息表
DROP TABLE IF EXISTS `datasource_config`;
CREATE TABLE IF NOT EXISTS `datasource_config`
(
`id` bigint(13) NOT NULL AUTO_INCREMENT COMMENT '主键',
`host` varchar(255) NOT NULL COMMENT '数据库地址',
`port` int(6) NOT NULL COMMENT '数据库端口',
`username` varchar(100) NOT NULL COMMENT '数据库用户名',
`password` varchar(100) NOT NULL COMMENT '数据库密码',
`database` varchar(100) DEFAULT 0 COMMENT '数据库名称',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='数据源配置表';
2.メインコード
2.1ポンポンファイル
ポンポンセクション情報ファイルには、依存関係とデータ依存関係接続を追加する必要があります
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2.2基本構成クラス
DatasourceConfig、主にコンテナ春に置かれDataSourceBuilderによってデータソースの彼自身の定義を構築します
@Configuration
public class DatasourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.type(DynamicDataSource.class);
return dataSourceBuilder.build();
}
}
主に `SqlSessionFactory`にMyBatisのデータ・ソース構成に前のステップを構築するMybatisConfiguration
@Configuration
@MapperScan(basePackages = "data.cloud.datas.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisConfiguration {
/**
* 创建会话工厂。
*
* @param dataSource 数据源
* @return 会话工厂
*/
@Bean(name = "sqlSessionFactory")
@SneakyThrows
public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
}
動的データソース2.3のメインロジック
現在ThreadLocalのスルー同じスレッドが変更されないことを保証するために使用される結合スレッドのDatasourceConfigContextHolderプライマリデータソースID
public class DatasourceConfigContextHolder {
private static final ThreadLocal<Long> DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID);
private static Long temp;
/**
* 设置默认数据源
*/
public static void setDefaultDatasource() {
DATASOURCE_HOLDER.remove();
setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID);
}
/**
* 获取当前数据源配置id
*/
public static Long getCurrentDatasourceConfig() {
//return DATASOURCE_HOLDER.get();
return temp;
}
/**
* 设置当前数据源配置id
*/
public static void setCurrentDatasourceConfig(Long id) {
DATASOURCE_HOLDER.set(id);
temp = id;
}
}
DynamicDataSource、主に動的にデータソース接続を切り替えるためのクラス継承 `com.zaxxer.hikari.HikariDataSource`、。
@Slf4j
public class DynamicDataSource extends HikariDataSource {
@Override
public Connection getConnection() throws SQLException {
// 获取当前数据源 id
Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig();
// 根据当前id获取数据源
HikariDataSource datasource = DatasourceHolder.INSTANCE.getDatasource(id);
if (null == datasource) {
datasource = initDatasource(id);
}
return datasource.getConnection();
}
/**
* 初始化数据源
* @param id 数据源id
* @return 数据源
*/
private HikariDataSource initDatasource(Long id) {
HikariDataSource dataSource = new HikariDataSource();
// 判断是否是默认数据源
if (DatasourceHolder.DEFAULT_ID.equals(id)) {
// 默认数据源根据 application.yml 配置的生成
DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class);
dataSource.setJdbcUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
} else {
// 不是默认数据源,通过缓存获取对应id的数据源的配置
SourceConfig sourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id);
if (sourceConfig == null) {
throw new RuntimeException("无此数据源");
}
dataSource.setJdbcUrl(sourceConfig.buildJdbcUrl());
dataSource.setUsername(sourceConfig.getUsername());
dataSource.setPassword(sourceConfig.getPassword());
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
}
// 将创建的数据源添加到数据源管理器中,绑定当前线程
DatasourceHolder.INSTANCE.addDatasource(id, dataSource);
return dataSource;
}
}
主にスケジューリングタスクに使用DatasourceScheduler
public enum DatasourceScheduler {
/**
* 当前实例
*/
INSTANCE;
private AtomicInteger cacheTaskNumber = new AtomicInteger(1);
private ScheduledExecutorService scheduler;
DatasourceScheduler() {
create();
}
private void create() {
this.shutdown();
this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("Datasource-Release-Task-%s", cacheTaskNumber.getAndIncrement())));
}
private void shutdown() {
if (null != this.scheduler) {
this.scheduler.shutdown();
}
}
public void schedule(Runnable task,long delay){
this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
}
}
DatasourceManagerは、主に、データソース、データソース記録された最後の使用時間を管理するために使用され、使用されない特定の時間をかけて、長時間使用したか否かを判断し、接続が解放されます。
public class DatasourceManager {
//默认释放时间
private static final Long DEFAULT_RELEASE = 10L;
//数据源
@Getter
private HikariDataSource dataSource;
public HikariDataSource getDataSource() {
return dataSource;
}
public void setDataSource(HikariDataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 上一次使用时间
*/
private LocalDateTime lastUseTime;
public DatasourceManager(HikariDataSource dataSource) {
this.dataSource = dataSource;
this.lastUseTime = LocalDateTime.now();
}
/**
* 是否已过期,如果过期则关闭数据源
* @return 是否过期,{@code true} 过期,{@code false} 未过期
*/
public boolean isExpired() {
if (LocalDateTime.now().isBefore(this.lastUseTime.plusMinutes(DEFAULT_RELEASE))) {
return false;
}
this.dataSource.close();
return true;
}
/**
* 刷新上次使用时间
*/
public void refreshTime() {
this.lastUseTime = LocalDateTime.now();
}
}
DatasourceHolder、クラスは主にを通じて、データソースを管理するために使用される `DatasourceScheduler`は、定期的にデータソースを長期間使用されていないかどうかを確認し、それが接続タイムアウトを解放します。
public enum DatasourceHolder {
//当前实例
INSTANCE;
//启动执行,定时5分钟清理一次
DatasourceHolder() {
DatasourceScheduler.INSTANCE.schedule(this::clearExpiredDatasource, 5 * 60 * 1000);
}
/**
* 默认数据源的id
*/
public static final Long DEFAULT_ID = -1L;
/**
* 管理动态数据源列表。
*/
private static final Map<Long, DatasourceManager> DATASOURCE_CACHE = new ConcurrentHashMap<>();
/**
* 添加动态数据源
* @param id 数据源id
* @param dataSource 数据源
*/
public synchronized void addDatasource(Long id, HikariDataSource dataSource) {
DatasourceManager datasourceManager = new DatasourceManager(dataSource);
DATASOURCE_CACHE.put(id, datasourceManager);
}
/**
* 查询动态数据源
* @param id 数据源id
* @return 数据源
*/
public synchronized HikariDataSource getDatasource(Long id) {
if (DATASOURCE_CACHE.containsKey(id)) {
DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id);
datasourceManager.refreshTime();
return datasourceManager.getDataSource();
}
return null;
}
/**
* 清除超时的数据源
*/
public synchronized void clearExpiredDatasource() {
DATASOURCE_CACHE.forEach((k, v) -> {
// 排除默认数据源
if (!DEFAULT_ID.equals(k)) {
if (v.isExpired()) {
DATASOURCE_CACHE.remove(k);
}
}
});
}
/**
* 清除动态数据源
* @param id 数据源id
*/
public synchronized void removeDatasource(Long id) {
if (DATASOURCE_CACHE.containsKey(id)) {
// 关闭数据源
DATASOURCE_CACHE.get(id).getDataSource().close();
// 移除缓存
DATASOURCE_CACHE.remove(id);
}
}
}
DatasourceConfigCache,该类主要用于缓存数据源的配置,用户生成数据源时,获取数据源连接参数。
public enum DatasourceConfigCache {
/**
* 当前实例
*/
INSTANCE;
/**
* 管理动态数据源列表。
*/
private static final Map<Long, SourceConfig> CONFIG_CACHE = new ConcurrentHashMap<>();
/**
* 添加数据源配置
*/
public synchronized void addConfig(Long id, SourceConfig config) {
CONFIG_CACHE.put(id, config);
}
/**
* 查询数据源配置
*/
public synchronized SourceConfig getConfig(Long id) {
if (CONFIG_CACHE.containsKey(id)) {
return CONFIG_CACHE.get(id);
}
return null;
}
// 清除数据源配置
public synchronized void removeConfig(Long id) {
CONFIG_CACHE.remove(id);
// 同步清除 DatasourceHolder 对应的数据源
DatasourceHolder.INSTANCE.removeDatasource(id);
}
}
2.4デフォルトのデータ・ソース構成とスイッチングデータソース
DefaultDatasource,默认数据源
//默认数据源
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefaultDatasource {
}
DatasourceSelectorAspect,主要用于切换数据源
@Aspect
@Component
public class DatasourceSelectorAspect {
@Pointcut("execution(public * data.cloud.datas.controller.*.*(..))")
public void datasourcePointcut(){
}
/**
* 前置操作,拦截具体请求,获取header里的数据源id,设置线程变量里,用于后续切换数据源
*/
@Before("datasourcePointcut()")
public void doBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
// 排除不可切换数据源的方法
DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class);
if (null != annotation) {
DatasourceConfigContextHolder.setDefaultDatasource();
} else {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
String configIdInHeader = request.getHeader("Datasource-Config-Id");
if (StringUtils.hasText(configIdInHeader)) {
long configId = Long.parseLong(configIdInHeader);
DatasourceConfigContextHolder.setCurrentDatasourceConfig(configId);
} else {
DatasourceConfigContextHolder.setDefaultDatasource();
}
}
}
/**
* 后置操作,设置回默认的数据源id
*/
@AfterReturning("datasourcePointcut()")
public void doAfter() {
DatasourceConfigContextHolder.setDefaultDatasource();
}
}
2.5ツール
SpringUtils
@Slf4j
@Service
@Lazy(false)
public class SpringUtil implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
private static Logger log = LoggerFactory.getLogger(SpringUtil.class);
//取得存储在静态变量中的ApplicationContext.
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//实现ApplicationContextAware接口, 注入Context到静态变量中.
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringUtil.applicationContext = applicationContext;
}
//从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
//从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
//清除SpringContextHolder中的ApplicationContext为Null.
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
/**
* 发布事件
* @param event 事件
*/
public static void publishEvent(ApplicationEvent event) {
if (applicationContext == null) {
return;
}
applicationContext.publishEvent(event);
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
public void destroy() {
SpringUtil.clearHolder();
}
}
2.6起動クラス
起動後、デフォルトのデータソースのクエリのデータソースの構成リストを使用し、後で使用するためでDatasourceConfigCache` `へのキャッシュ、。
@SpringBootApplication
@EnableSwagger2
@EnableTransactionManagement
public class DatasApplication implements CommandLineRunner {
@Resource
private ConfigMapper configMapper;
public static void main(String[] args) {
SpringApplication.run(DatasApplication.class, args);
}
@Override
public void run(String... args) {
// 设置默认的数据源
DatasourceConfigContextHolder.setDefaultDatasource();
// 查询所有数据库配置列表
List<SourceConfig> sourceConfigs = configMapper.selectAll();
System.out.println("加载其余数据源配置列表: " + sourceConfigs);
// 将数据库配置加入缓存
sourceConfigs.forEach(config -> DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config));
}
}
3.テスト
要求ヘッダーの値データソース-CONFIG-IDヘッダを追加し、値は、ソース・テーブル内のデータのid値であります