动态数据源

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. EntLib 自动数据库连接字符串加密

    const string provider = "RsaProtectedConfigurationProvider"; Configuration config = null; ...

  2. 【转】Windows下charles 使用教程指南

    1.下载就不用再说了,网上好多破解的安装包 2.下面是pc端的抓包使用情况 Charles支持抓去http.https协议的请求,不支持socket.然后charles会自动配置IE浏览器和工具的代理 ...

  3. Git之右键没有Git Bash Here的解决办法

    1.Win+R 打开运行输入regedit 回车打开注册表 2.找到[HKEY_CLASSES_ROOT\Directory\Background]. 3.在[Background]下如果没有[she ...

  4. CMMI的敏捷开发

  5. SpringSecurity兑现多登录成功页面和登录成功返回被拦截界面

    SpringSecurity实现多登录成功页面和登录成功返回被拦截界面 使用SrpingSceurity作为认证和授权的安全框架可以省下很多基础工作. 具体可以参考SpringSecurity,这里不 ...

  6. mybatis中批量插入以及更新

    1:批量插入 批量插入就是在预编译的时候,将代码进行拼接,然后在数据库执行 <insert id="batchInsert" parameterType="java ...

  7. osx 10.11 一键制作U盘傻瓜工具最新版 无需任何命令

    osx 10.11 最新版U盘制作工具   无需任何命令   纯傻瓜式  !!!只要把app下载下来放在应用程序  鼠标点点就可以做了... 下载地址:http://diskmakerx.com/do ...

  8. css3整理--background-clip

    background-clip语法: background-clip : border-box || padding-box || content-box 参数取值: border-box:此值为默认 ...

  9. PHP跳出循环的方法及continue、break、exit的区别

    PHP中的循环结构大致有for循环,while循环,do{} while 循环以及foreach循环几种,不管哪种循环中,在PHP中跳出循环大致有这么几种方式: <?php $i = 1; wh ...

  10. i.e., e.g., etc.

    经常搞混的一些英语缩写,以及他们的应用规范. i.e. (id est) 含义为:也就是说,that is to say, in other words e.g. (exampli gratia) 含 ...