动态数据源

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动态数据源方案的更多相关文章

  1. (43). Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...

  2. 43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    [视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm ...

  3. SaaS 系统架构,Spring Boot 动态数据源实现!

    这段时候在准备从零开始做一套SaaS系统,之前的经验都是开发单数据库系统并没有接触过SaaS系统,所以接到这个任务的时候也有也些头疼,不过办法部比困难多,难得的机会. 在网上找了很多关于SaaS的资料 ...

  4. Spring Boot 动态数据源(多数据源自己主动切换)

    本文实现案例场景: 某系统除了须要从自己的主要数据库上读取和管理数据外.另一部分业务涉及到其它多个数据库,要求能够在不论什么方法上能够灵活指定详细要操作的数据库. 为了在开发中以最简单的方法使用,本文 ...

  5. Spring Boot 动态数据源(Spring 注解数据源)

    本文实现案例场景:某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基于 ...

  6. Spring Boot 动态数据源(多数据源自动切换)

    本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基 ...

  7. 22. Spring Boot 动态数据源(多数据源自动切换)

    转自:https://blog.csdn.net/catoop/article/details/50575038

  8. Spring实现动态数据源,支持动态加入、删除和设置权重及读写分离

    当项目慢慢变大,訪问量也慢慢变大的时候.就难免的要使用多个数据源和设置读写分离了. 在开题之前先说明下,由于项目多是使用Spring,因此下面说到某些操作可能会依赖于Spring. 在我经历过的项目中 ...

  9. Spring Boot多数据源配置(二)MongoDB

    在Spring Boot多数据源配置(一)durid.mysql.jpa 整合中已经讲过了Spring Boot如何配置mysql多数据源.本篇文章讲一下Spring Boot如何配置mongoDB多 ...

随机推荐

  1. Android Launcher分析和修改12——Widget列表信息收集

    很久没写Launcher分析的文章,最近实在太忙.今天七夕本来是想陪女朋友逛街 ,碰巧打台风呆在家里,就继续写一篇文章.今天主要是讲一下Launcher里面的Widget列表,这方面信息比较多,今天重 ...

  2. Install elasticsearch-head: – for Elasticsearch 5.x

    Running as a plugin of Elasticsearch Install elasticsearch-head:– for Elasticsearch 5.x:site plugins ...

  3. Vue.js常用指令:v-model

    一.v-model指令 v-model 用来获取表单元素的值.对应input输入框获取的是输入的值,单选按钮.复选框.下拉框获取的是选择的状态. 代码示例如下: <!DOCTYPE html&g ...

  4. 安卓程序代写 网上程序代写[原]Android应用的自动更新模块

    软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很 ...

  5. Linux磁盘概念及其管理工具fdisk

    Linux磁盘概念及其管理工具fdisk [日期:2016-08-27] 来源:Linux社区  作者:chawan [字体:大 中 小]   引言:冯诺依曼体系中的数据存储器就是我们常说的磁盘或硬盘 ...

  6. (原)android系统下绑定Server的时候报MainActivity has leaked ServiceConnection的错误

    今天在android系统下根据官方的demo代码,我们需要启动一个服务,并绑定,但在程序启动以后,老是报错:   Activity MainActivity has leaked ServiceCon ...

  7. php中urlencode和urldecode的用法

    URLEncode:是指针对网页url中的中文字符的一种编码转化方式,最常见的就是Baidu.Google等搜索引擎中输入中文查询时候,生成经过Encode过的网页URL.URLEncode的方式一般 ...

  8. @Transactional(readOnly=true) in Spring

    http://www.skill-guru.com/blog/2010/12/19/transactionalreadonlytrue-in-spring/ @Transactional(readOn ...

  9. M0 M4时钟控制(一)

    时钟控制器为整个芯片提供时钟源,包括系统时钟和所有外围设备时钟.该控制器还通过单独时钟的开或关,时钟源选择和分频器来进行功耗控制.在CPU使能低功耗PDEN(CLK_PWRCTL[7]) 位和Cort ...

  10. 【netcore入门】在Windows IIS上部署.NET Core 2.1项目

    部署之前先检查下面2个先决条件是否满足 1.安装了 IIS 模块 win7 在 控制面板→程序和功能→打开或关闭Windows功能→勾选Internet 信息服务(Internet Informati ...