您的位置 首页 java

Spring 动态切换、添加数据源实现以及源码浅析

公司项目需求,由于要兼容老系统的数据库结构,需要搭建一个 可以动态切换、添加数据源的后端服务。

参考了过去的项目,通过配置多个SqlSessionFactory 来实现多数据源,这么做的话,未免过于笨重,而且无法实现动态添加数据源这个需求。

Spring 动态切换、添加数据源实现以及源码浅析

通过 Spring AbstractRoutingDataSource 为我们抽象了一个 DynamicDataSource 解决这一问题

Spring 动态切换、添加数据源实现以及源码浅析

简单分析下 AbstractRoutingDataSource 的源码

Spring 动态切换、添加数据源实现以及源码浅析

Spring 动态切换、添加数据源实现以及源码浅析

targetDataSources 就是我们的多个数据源,在初始化的时候会调用afterPropertiesSet(),去解析我们的数据源 然后 put 到 resolvedDataSources

实现了 DataSource 的 getConnection(); 我们看看 determineTargetDataSource(); 做了什么

通过下面的 determineCurrentLookupKey();(这个方法需要我们实现) 返回一个key,然后从 resolvedDataSources (其实也就是 targetDataSources) 中 get 一个数据源,实现了每次调用 getConnection(); 打开连接 切换数据源,如果想动态添加的话 只需要重新 set targetDataSources 再调用 afterPropertiesSet() 即可

Talk is cheap. Show me the code

我使用的springboot版本为 1.5.x,下面是核心代码

完整代码:

/**
 * 多数据源配置
 * 
 * @author Taven
 *
 */@Configuration
@MapperScan("com.gitee.taven.mapper")
public class DataSourceConfigurer {
 /**
 * DataSource 自动配置并注册
 *
 * @return data source
 */ @Bean("db0")
 @Primary
 @ConfigurationProperties(prefix = "datasource.db0")
 public DataSource dataSource0() {
 return DruidDataSource build er.create().build();
 }
 /**
 * DataSource 自动配置并注册
 *
 * @return data source
 */ @Bean("db1")
 @ConfigurationProperties(prefix = "datasource.db1")
 public DataSource dataSource1() {
 return DruidDataSourceBuilder.create().build();
 }
 /**
 * 注册动态数据源
 * 
 * @return
 */ @Bean("dynamicDataSource")
 public DataSource dynamicDataSource() {
 DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
 Map<Object, Object> dataSourceMap = new HashMap<>();
 dataSourceMap.put("dynamic_db0", dataSource0());
 dataSourceMap.put("dynamic_db1", dataSource1());
 dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource0());// 设置默认数据源
 dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
 return dynamicRoutingDataSource;
 }
 /**
 * Sql session factory bean.
 * Here to config datasource for SqlSessionFactory
 * <p>
 * You need to add @{@code @ConfigurationProperties(prefix = "mybatis")}, if you are using *.xml file,
 * the {@code 'mybatis.type-aliases-package'} and {@code 'mybatis.mapper-locations'} should be set in
 * {@code 'application.properties'} file, or there will appear invalid bond statement exception
 *
 * @return the sql session factory bean
 */ @Bean
 @ConfigurationProperties(prefix = "mybatis")
 public SqlSessionFactoryBean sqlSessionFactoryBean() {
 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
 // 必须将动态数据源添加到 sqlSessionFactoryBean
 sqlSessionFactoryBean.setDataSource(dynamicDataSource());
 return sqlSessionFactoryBean;
 }
 /**
 * 事务管理器
 *
 * @return the platform transaction manager
 */ @Bean
 public PlatformTransactionManager transactionManager() {
 return new DataSourceTransactionManager(dynamicDataSource());
 }
}
 

通过 ThreadLocal 获取线程安全的数据源 key

package com.gitee.taven.config;
public class DynamicDataSourceContextHolder {
 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
 @Override
 protected String initialValue() {
 return "dynamic_db0";
 }
 };
 /**
 * To switch DataSource
 *
 * @param key the key
 */ public static void setDataSourceKey(String key) {
 contextHolder.set(key);
 }
 /**
 * Get current DataSource
 *
 * @return data source key
 */ public static String getDataSourceKey() {
 return contextHolder.get();
 }
 /**
 * To set DataSource as default
 */ public static void clearDataSourceKey() {
 contextHolder.remove();
 }
}
 

动态 添加、切换数据源

/**
 * 动态数据源
 * 
 * @author Taven
 *
 */public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
 private final Logger logger = LoggerFactory.getLogger(getClass());
 private static Map<Object, Object> targetDataSources = new HashMap<>();
 /**
 * 设置当前数据源
 *
 * @return
 */ @Override
 protected Object determineCurrentLookupKey() {
 logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
 return DynamicDataSourceContextHolder.getDataSourceKey();
 }
 @Override
 public void setTargetDataSources(Map<Object, Object> targetDataSources) {
 super.setTargetDataSources(targetDataSources);
 DynamicRoutingDataSource.targetDataSources = targetDataSources;
 }
 /**
 * 是否存在当前key的 DataSource
 * 
 * @param key
 * @return 存在返回 true, 不存在返回 false
 */ public static boolean isExistDataSource(String key) {
 return targetDataSources.containsKey(key);
 }
 /**
 * 动态增加数据源
 * 
 * @param map 数据源属性
 * @return
 */ public synchronized boolean addDataSource(Map<String, String> map) {
 try {
 Connection connection = null;
 // 排除连接不上的错误
 try { 
 Class.forName(map.get(DruidDataSourceFactory.PROP_DRIVERCLASSNAME));
 connection = DriverManager.getConnection(
 map.get(DruidDataSourceFactory.PROP_URL), 
 map.get(DruidDataSourceFactory.PROP_USERNAME),
 map.get(DruidDataSourceFactory.PROP_PASSWORD));
 System.out.println(connection.isClosed());
 } catch (Exception e) {
 return false;
 } finally {
 if (connection != null && !connection.isClosed()) 
 connection.close();
 }
 String database = map.get("database");//获取要添加的数据库名
 if (StringUtils.isBlank(database)) return false;
 if (DynamicRoutingDataSource.isExistDataSource(database)) return true; 
 DruidDataSource druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(map);
 druidDataSource.init();
 Map<Object, Object> targetMap = DynamicRoutingDataSource.targetDataSources;
 targetMap.put(database, druidDataSource);
 // 当前 targetDataSources 与 父类 targetDataSources 为同一对象 所以不需要set
// this.setTargetDataSources(targetMap);
 this.afterPropertiesSet();
 logger.info("dataSource {} has been added", database);
 } catch (Exception e) {
 logger.error(e. getMessage ());
 return false;
 }
 return true;
 }
}
 

可以通过 AOP 或者 手动 DynamicDataSourceContextHolder.setDataSourceKey(String key) 切换数据源。

需要注意的:当我们开启了事务之后,是无法在去切换数据源的。

本文项目源码:

参考文献:

原文:www.jianshu.com/p/0a485c965b8b
 

回复“资源”,领取 练手源码,视频教程,微服务、并发,数据可调优等,搜索【Java知音】

文章来源:智云一二三科技

文章标题:Spring 动态切换、添加数据源实现以及源码浅析

文章地址:https://www.zhihuclub.com/193052.shtml

关于作者: 智云科技

热门文章

网站地图