spring实现动态切换、添加数据源及源码分析

网友投稿 369 2023-01-22


spring实现动态切换、添加数据源及源码分析

前言

对于数据量在1千万,单个mysql数据库就可以支持,但是如果数据量大于这个数的时候,例如1亿,那么查询的性能就会很低。此时需要对数据库做水平切分,常见的做法是按照用户的账号进行hash,然后选择对应的数据库。

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

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

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

简单分析下 AbstractRoutingDataSource 的源码

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,下面是核心代码

完整代码:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下载)

/**

* 多数据源配置

*

* @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 DruidDataSourceBuilder.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 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

*

* 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

*/

@Bhttp://ean

@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 contextHolder = new ThreadLocal() {

@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 targetDataSources = new HashMap<>();

/**

* 设置当前数据源

*

* @return

*/

@Override

protected Object determineCurrentLookupKey() {

logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());

return DynamicDataSourceContextHolder.getDataSourceKey();

}

@Override

public void setTargetDataSources(Map 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 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 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) 切换数据源

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

本文项目源码:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下载)

参考文献:https://github.com/helloworlde/SpringBoot-DynamicDataSource

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用IDEA和Gradle构建Vertx项目(图文步骤)
下一篇:打造高效项目研发管理平台(打造行业领先研发平台)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~