spring boot动态数据源方案
动态数据源
1.背景
动态数据源在实际的业务场景下需求很多,而且想要沟通多数据库确实需要封装这种工具,针对于bi工具可能涉及到从不同的业务库或者数据仓库中获取数据,动态数据源就更加有意义。
2.依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<dependency>
<groupId>com.viewhigh.bi.common</groupId>
<artifactId>common</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
3.多数据源原理解析
① 在应用程序启动的时候初始化默认数据源,并将默认数据源注册到spring上下文中,在这过程中需要实现EnvironmentAware接口中的setEnvironment方法,我们知道setEnvironment方法会在初始化上下文的时候调用,那么利用这个时机就可以根据配置文件初始化默认数据源了,当然可以初始化1个也可以多个。
/**
* Created by zzq on 2017/6/14.
* 负责初始化数据源配置
*/
public class DataSourceRegister<T> implements EnvironmentAware, ImportBeanDefinitionRegistrar {
private javax.sql.DataSource defaultTargetDataSource;
static final String MAINDATASOURCE = "mainDataSource"; public final void setEnvironment(Environment environment) {
DruidEntity druidEntity = FileUtil.readYmlByClassPath("db_info", DruidEntity.class); defaultTargetDataSource = DataSourceUtil.createMainDataSource(druidEntity);
} public final void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 0.将主数据源添加到数据源集合中
DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE, defaultTargetDataSource);
//1.创建DataSourceBean
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//spring名称约定为defaultTargetDataSource和targetDataSources
mpv.addPropertyValue("defaultTargetDataSource", defaultTargetDataSource);
mpv.addPropertyValue("targetDataSources", DataSourceSet.getTargetDataSourcesMap());
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
}
}
在上述代码中注册Data SourceBean时可以指定一个默认数据源,这个数据源就是默认使用的存储于defaultTargetDataSource,而其它的数据源则存在targetDataSources。
② 那么如果想要使用其它数据源就需要在targetDataSources中通过指定的key去切换就可以。在此之前需要重写Spring中AbstractRoutingDataSource类型的determineCurrentLookupKey方法,而返回值则是即将启动数据源所对应的key,这样就达到了多个数据源切换的目的。
/**
* Created by zzq on 2017/6/13.
*/
public class DataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String keyDataSource = DataSourceSet.getCurrDataSource();
LogUtil.info("***当前数据源为[{}]", keyDataSource == null ? "默认数据源" : keyDataSource);
return keyDataSource;
}
}
4.设计方案
- 使用方式:
1) 通在应用程序启动时找到一个初始化时机,并使用import导入数据源注册类即可
@Import({DataSourceRegister.class})
@SpringBootApplication//(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class})
@ComponentScan("com.XXX.bi")
//@EnableCaching
public class BiApplication {
public static void main(String[] args) {
LogUtil.setEnabled(true);//开启日志输出
SpringApplication sa = new SpringApplication(BiApplication.class);
sa.setBannerMode(Banner.Mode.LOG);
sa.run(args);
}
}
2) 通过注解方式使用
注解方式比较容易理解,但相对于代码而言处理不够灵活;示例如下:
@ActivateDataSource("001")
public List findAll() {
String sql = "select * from td_bi_datasourcetype where is_remove=0 and organization_id=? ";
Map map = new HashMap();
map.put("id", String.class);
map.put("code", String.class);
map.put("remark", String.class);
map.put("name", String.class);
map.put("is_remove", Integer.class);
DynamicBean dynamicBean = new DynamicBean(map);
String orgId = Identity.getOrganizationId();
return jdbcTemplateExtend.query(sql, new Object[]{orgId}, dynamicBean.getObject().getClass());
}
提供了在方法开始时标记注解,并指定数据源key,为注解参数,则在方法调用过程中即可使用当前key所对应的数据源。
内幕相信你已经猜到了,我们在数据源初始化的时候维护了一个DataSourceSet集合,该集合中存储了数据源key和对应实际的DataSource对象。而且在contextHolder中存储了当前已经设置的数据源key值,这样在触发查询方法时直接调用了系统determineCurrentLookupKey方法,则在这个方法中使用了contextHolder的key值;
/**
* Created by zzq on 2017/6/13.
*/
public class DataSourceSet {
private static final ThreadLocal<String> contextHolder = new ThreadLocal(); private static List<String> dataSourceKeyList = new CopyOnWriteArrayList<String>(); private static Map targetDataSourcesMap = new ConcurrentHashMap(); public static Object putTargetDataSourcesMap(Object key, Object dataSource) {
dataSourceKeyList.add(key.toString());
return targetDataSourcesMap.put(key, dataSource);
} public static Object removeTargetDataSourcesMap(Object key) {
try {
dataSourceKeyList.remove(key);
return targetDataSourcesMap.remove(key);
} catch (Exception e) {
e.printStackTrace();
throw new CustomException(00000, "移除DataSourceSet数据源信息时出现异常,可能由于dataSourceKeyList或targetDataSourcesMap没有该item项");
}
} public static Map getTargetDataSourcesMap() {
return targetDataSourcesMap;
} public static void setCurrDataSource(String ds) {
contextHolder.set(ds);
} public static String getCurrDataSource() {
return contextHolder.get();
} public static void clearCurrDataSource() {
contextHolder.remove();
} public static boolean containsDataSource(String dataSourceKey) {
return dataSourceKeyList.contains(dataSourceKey);
}
}
这样就可以在aspectJ的aop环绕方式中,方法开始时调用DataSourceSet的设置数据源key来达到切换数据源的目的,在方法调用结束后调用重置key的方法来切换回原来的数据源;
public class DataSourceAspect {
@Before("@annotation(ads)")
public void activateDataSource(JoinPoint point, ActivateDataSource ads) throws Throwable {
String keyDataSource = ads.value();
if (!process(keyDataSource, point))
return;
LogUtil.info("method:{} ", point.getSignature().getName());
DataSourceUtil.activateDataSource(keyDataSource, null);
}
@After("@annotation(ads)")
public void resetDataSource(JoinPoint point, ActivateDataSource ads) {
String keyDataSource = ads.value();
if (!process(keyDataSource, point))
return;
LogUtil.info("method:{} ", point.getSignature().getName());
DataSourceUtil.resetDataSource(keyDataSource);
}
private boolean process(String keyDataSource, JoinPoint point) {
if (keyDataSource == null) {
LogUtil.info("数据源注解已经标识,但value为null[{}]", point.getSignature().getName());
return false;
}
if (keyDataSource.equals(DataSourceRegister.MAINDATASOURCE)) return false;
return true;
}
}
而在DataSourceUtil中则封装了数据源创建时的一系列动作;那么这个时候你也很有可能会发问,应用程序在启动时会创建一次数据源,如果在程序运行期动态创建数据源怎么办呢,下面就可以揭开这个问题:
/**
* 从bean获取数据源
*
* @param keyDataSource
* @return
*/
private static DataSource loadDataSource(String keyDataSource) {
if (dataSourceGetStrategy == null) {
synchronized (DataSourceUtil.class) {
if (dataSourceGetStrategy == null) {
if (!App.getContext().containsBeanDefinition(DATASOURCEGETSTRATEGY))
throw new CustomException(ResType.OverrideGetDataSourceInfo);
dataSourceGetStrategy = (DataSourceGetStrategy) App.getContext().getBean(DATASOURCEGETSTRATEGY);
}
}
}
return dataSourceGetStrategy.getDataSource(keyDataSource);
}
那么在数据源帮助类中提供了一个抽象类:
/**
* 该抽象类必须由子类实现其抽象方法,用于负责动态数据源信息获取
* <p>
* Created by zzq on 2017/6/19.
*/
public abstract class DataSourceGetStrategy {
public abstract javax.sql.DataSource getDataSource(String keyDataSource); @Bean(name = DataSourceUtil.DATASOURCEGETSTRATEGY)
public DataSourceGetStrategy getDataSourceReadStrategy() {
return this;
}
}
如果想要使用动态数据源框架则必须实现其getDataSource方法,那么在这个方法中你可以获取之前传入的datasourcekey,就可以按照自己的方式创建数据源了,如下示例为创建了一个阿里的druid数据源:
/**
* Created by zzq on 2017/6/19.
*/
@Configuration
public class GetDataSource extends DataSourceGetStrategy {
@Autowired
private JdbcTemplateExtend jdbcTemplateExtend; @Override
public DataSource getDataSource(String keyDataSource) {
String sql = "select t1.url,t1.userName,t1.`password`,t2.driverClassName from " +
"td_bi_datasource t1 inner join td_bi_datasourcetype t2 on " +
"t1.dataSourceType_id=t2.id where " +
"t1.`id`=? and t1.is_remove=0 AND t2.is_remove=0 and t1.organization_id=? and t2.organization_id=?"; String orgId = Identity.getOrganizationId(); List<DataSourceAndType> dataSourceInfoList = jdbcTemplateExtend.query(sql, new Object[]{keyDataSource, orgId, orgId}, DataSourceAndType.class);
DataSourceAndType dataSourceInfoEntity = null;
if (dataSourceInfoList.size() > 0)
dataSourceInfoEntity = dataSourceInfoList.get(0); if (dataSourceInfoEntity == null)
return null;
DruidDataSource datasource = new DruidDataSource(); datasource.setUrl(dataSourceInfoEntity.getUrl()); dbType.put(keyDataSource, dataSourceInfoEntity.getUrl()); datasource.setUsername(dataSourceInfoEntity.getUserName());
datasource.setPassword(dataSourceInfoEntity.getPassword());
datasource.setDriverClassName(dataSourceInfoEntity.getDriverClassName());
datasource.setMaxWait(13000);
return datasource;
}
}
3) 代码调用方式使用
相信代码调用的方式会让更多人感觉比较舒适吧!
和aspect类似的道理,如下代码:
try {
DataSourceUtil.activateDataSource(dataSourceKey, dataSource);
//做自己的事情
} finally {
DataSourceUtil.resetDataSource(dataSourceKey);
}
常规方式可以使用try finally处理,如果你有更好的方式也可以使用哦!思路就是在你的代码前激活数据源,在自己代码调用最后释放数据源。
PS:
① 在最后强调下,不用担心频繁创建数据源之后的性能问题,因为在一次创建之后,多次使用时DataSourceSet会有保存记录,直接切换数据源,不会有任何的性能消耗;
② 如果有临时数据源不希望被缓存则使用DataSourceUtil.activateDataSource(dataSourceKey, dataSource);两个参数的方法重载,第二个参数可以直接自己创建数据源对象传入,使用之后,框架也会将资源释放不做保留;
③ SpringBoot动态数据源中的Bean名称为:dataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//spring名称约定为defaultTargetDataSource和targetDataSources
mpv.addPropertyValue("defaultTargetDataSource", defaultTargetDataSource);
mpv.addPropertyValue("targetDataSources", DataSourceSet.getTargetDataSourcesMap());
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
项目地址:https://github.com/qq472708969/dynamicDataSource !
spring boot动态数据源方案的更多相关文章
- (43). Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】
在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...
- 43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】
[视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm ...
- SaaS 系统架构,Spring Boot 动态数据源实现!
这段时候在准备从零开始做一套SaaS系统,之前的经验都是开发单数据库系统并没有接触过SaaS系统,所以接到这个任务的时候也有也些头疼,不过办法部比困难多,难得的机会. 在网上找了很多关于SaaS的资料 ...
- Spring Boot 动态数据源(多数据源自己主动切换)
本文实现案例场景: 某系统除了须要从自己的主要数据库上读取和管理数据外.另一部分业务涉及到其它多个数据库,要求能够在不论什么方法上能够灵活指定详细要操作的数据库. 为了在开发中以最简单的方法使用,本文 ...
- Spring Boot 动态数据源(Spring 注解数据源)
本文实现案例场景:某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基于 ...
- Spring Boot 动态数据源(多数据源自动切换)
本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基 ...
- 22. Spring Boot 动态数据源(多数据源自动切换)
转自:https://blog.csdn.net/catoop/article/details/50575038
- Spring实现动态数据源,支持动态加入、删除和设置权重及读写分离
当项目慢慢变大,訪问量也慢慢变大的时候.就难免的要使用多个数据源和设置读写分离了. 在开题之前先说明下,由于项目多是使用Spring,因此下面说到某些操作可能会依赖于Spring. 在我经历过的项目中 ...
- Spring Boot多数据源配置(二)MongoDB
在Spring Boot多数据源配置(一)durid.mysql.jpa 整合中已经讲过了Spring Boot如何配置mysql多数据源.本篇文章讲一下Spring Boot如何配置mongoDB多 ...
随机推荐
- 搭建redis集群
官方详细介绍请移步:http://www.redis.cn/topics/cluster-tutorial.html 这里总结性给出搭建步骤: 1. 至少6个节点,三主三从 2. 编译redis源码 ...
- python 中的map,dict,lambda,reduce,filter
1.map(function,sequence) 对sequence 中的item依次执行function(item), 见执行结果组成一个List返回 例如: #!/usr/bin/python # ...
- 【iCore4 双核心板_FPGA】例程十六:基于双口RAM的ARM+FPGA数据存取实验
实验现象: 核心代码: int main(void) { /* USER CODE BEGIN 1 */ int i; int address,data; ; ]; ]; char *p; /* US ...
- Java知多少(54)断言详解
断言的概念 断言用于证明和测试程序的假设,比如“这里的值大于 5”.断言可以在运行时从代码中完全删除,所以对代码的运行速度没有影响. 断言的使用 断言有两种方法: 一种是 assert<< ...
- Docker for Windows 代理设置(linux container)
https://blog.csdn.net/mzhangsf/article/details/79747979
- Spark学习笔记——Spark上数据的获取、处理和准备
数据获得的方式多种多样,常用的公开数据集包括: 1.UCL机器学习知识库:包括近300个不同大小和类型的数据集,可用于分类.回归.聚类和推荐系统任务.数据集列表位于:http://archive.ic ...
- 【QT】对话框打开图像并用QPixmap显示
绘图设备是指继承QPaintDevice的子类,可以使用QPainter直接在其上面绘制图形,Qt一共提供了四个这样继承QPaintDevice的绘图设备类. 分别是QPixmap.QBitmap.Q ...
- python实现微信接口——itchat模块
python实现微信接口——itchat模块 安装 sudo pip install itchat 登录 itchat.auto_login() 这种方法将会通过微信扫描二维码登录,但是这种登录的方 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【CF944G】Coins Exhibition DP+队列
[CF944G]Coins Exhibition 题意:Jack去年参加了一个珍稀硬币的展览会.Jack记得一共有 $k$ 枚硬币,这些硬币排成一行,从左到右标号为 $1$ 到 $k$ ,每枚硬币是正 ...