spring动态创建数据源
在最近的项目业务中,需要在程序的运行过程中,添加新的数据库添链接进来,然后从新数据库链接中读取数据。
网上查阅了资料,发现spring为多数据源提供了一个抽象类AbstractRoutingDataSource,该类中只有一个抽象方法determineCurrentLookupKey()需要由我们实现。
以下是使用方法
假设我们创建一个类DynimaticDataSource,继承AbstractRoutingDataSource,并重写determineCurrentLookupKey()方法。
spring启动初始化DynimaticDataSource:
1、可以通过spring的自动注入,对AbstractRoutingDataSource类注入相应的属性,注入属性不是必须的,可以通过继承的子类重写这些方法来重新设置对这些属性的调用。
2、注入后spring会执行protected方法afterPropertiesSet(),该方法先判断属性targetDataSources不能为null,即必须初始化时注入至少一个数据源,否则抛出异常"Property 'targetDataSources' is required"。然后将该属性(类型为map<object,object>)的值全部转换到属性resolvedDataSources(类型为map<Object, DataSource>)中去。如果属性defaultTargetDataSource不为null,即已经设置默认数据源,将其转换为DataSource类型并赋值给属性defaultTargetDataSource。
经过以上处理后,属性resolvedDataSources中会被存放我们添加的数据源,该属性是一个map集合,key为Object类型,value为数据源。同时可能会有一个默认数据源(可以注入也可以不注入)。
使用DynimaticDataSource类获取连接:
1、调用该类的public方法getConnection()来获取连接。
2、getConnection在抽象类中被重写,会先调用protected方法determineTargetDataSource()。该方法先判断属性resolvedDataSources不为null,即初始化时候注入了至少一个数据源,否则抛出异常"DataSource router not initialized"。然后调用由子类重写的抽象方法determineCurrentLookupKey()获取dataSource在resolvedDataSources中对应的key,判断使用哪个数据源。
3、根据key从resolvedDataSources中获取数据源,如果resolvedDataSources中不存在,再判断lenientFallback为true(默认为true,可以设置)或key为null,返回默认数据源resolvedDefaultDataSource。否则抛出异常"Cannot determine target DataSource for lookup key [" + key+ "]"。
4、调用获取数据源的getConnection()方法获取连接。
在初始化时指定多数据源案例代码:
1、创建一个类DynimaticDataSource,继承AbstractRoutingDataSource,并重写determineCurrentLookupKey()方法。该方法负责判断当前线程使用哪一种数据源。这是最简单的一种实现方法,不重写任何非抽象方法,但必须在初始化时配置至少一个的数据源。
public class DynamicDatasource extends AbstractRoutingDataSource{ private static Map<Object, Object> targetDataSources; protected DruidDataSource dataSource; @Override
protected String determineCurrentLookupKey() {
//指定使用哪个数据源
return DataSourceUtils.getDbtype();
}
}
2、srping配置文件(部分)
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="maxActive" value="50" /><!-- 最大连接池数 -->
<property name="minIdle" value="5" /><!-- 最小连接池数 -->
</bean> <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.url2}" />
<property name="username" value="${jdbc.username2}" />
<property name="password" value="${jdbc.password2}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="maxActive" value="50" /><!-- 最大连接池数 -->
<property name="minIdle" value="5" /><!-- 最小连接池数 -->
</bean> <bean id="multDataSource" class="com.ftpSystem.dao.DynamicDatasource">
<property name="targetDataSources">
<map >
<entry value-ref="dataSource" key="masterDataSource"></entry>
<entry value-ref="dataSource2" key="yangDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource"></property>
</bean>
3、使用数据源,我这里使用jdbcTemple,可以使用其他持久层框架,方式同单数据源配置一致。
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="multDataSource"></property>
</bean>
4、切换数据源类
public class DataSourceUtils { private static final ThreadLocal<String> local = new ThreadLocal<String>(); public static String getDbtype() {
return local.get();
} public static void setDbtype(String dbtype) {
local.set(dbtype);
} public static void clear() {
local.remove();
}
}
5、客户端代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-dao.xml")
public class TestDao { @Autowired
private JdbcTemplate jdbcTemplate; @Test
public void Test(){
System.out.println("ok");
int i = (Integer) jdbcTemplate.queryForObject("select count(*) from t_gg_zsdw", Integer.class);
System.out.println("i="+i);
DataSourceUtils.setDbtype("dataSource2");//切换数据源至dataSource2
i = (Integer) jdbcTemplate.queryForObject("select count(*) from t_gg_zsdw", Integer.class);
System.out.println("i="+i);
}
}
由于我们已经在spring的配置文件中指定了属性defaultTargetDataSource,因此程序会默认使用该数据源。然后我们执行一次后切换为dataSource2,之后的执行会改为使用dataSource2的数据源。
以上方式可以对程序配置多数据源,但是缺点在程序初始化之时就指定所有的数据源,无法在运行时动态添加。
程序动态配置多数据源:
通过最上面我们对获取DynimaticDataSource对象的数据源连接的过程分析可知,AbstractRoutingDataSource类是通过determineTargetDataSource()方法来获取数据源。在该方法里面,又使用了我们重写的抽象方法determineCurrentLookupKey()来判断使用哪一个数据源的key,通过这个key,在数据源初始化后存放的集合resolvedDataSources中获取想要的数据源。
因此,如果我们想要动态的添加数据源进去,有两种思路可以考虑,一是重写determineTargetDataSource()方法,并且我们自己配置一个数据源集合,通过该方法,调用我们自己的数据源集合中对应的数据源。二是在resolvedDataSources属性中动态添加key-value进去,可以在determineTargetDataSource()方法中获取该数据源即可。
通过对源码查看可知,第二种思路所需的属性resolvedDataSources是私有的,且类中没有提供相应的get方法获取。因此只能采用第一种方式。
样例如下:
先是继承AbstractRoutingDataSource的实体类
/**
* 数据源集合管理类
* 使用数据源之前必须先添加,然后指定使用哪个数据源
* @author yangxiangfeng
*
*/
public class DynamicDatasource extends AbstractRoutingDataSource { private static Map<String, DataSource> dataSourceMap = new HashMap<String, DataSource>(); private final static Map<DataSourceEntity, String> dseMap = new HashMap<>();
//spring注入需要set方法,不是必要的,可以注入也可以不注入
public static void setDataSourceMap(Map<String, DataSource> dataSourceMap) {
DynamicDatasource.dataSourceMap = dataSourceMap;
}
//获取数据源信息集合
public static Map<DataSourceEntity, String> getDsemap() {
return dseMap;
} //检查是否包含指定id的数据源
public static boolean checkDbKey(String dbKey){
if(dataSourceMap.get(dbKey) != null)return true;
return false;
}
//抽象方法,必须重写,用来判断使用哪个数据源
@Override
protected String determineCurrentLookupKey() {
return DataSourceUtils.getDbKey();
} /**
* 对数据源的初始化方法,由于这里已经将数据源集合放在本类中,如果不重写将会由于父类参数为null而抛出异常。
*/
@Override
public void afterPropertiesSet() {} /**
* 确定使用哪一个数据源
* 这里不做null判断,因为是经过null判断后再进入的。
*/
@Override
protected DataSource determineTargetDataSource() {
System.out.println("tttt");
String dsKey = determineCurrentLookupKey();
DataSource dds = dataSourceMap.get(dsKey);
return dds;
} /**
* 添加数据源
* 为了防止多线程添加同一个数据源,这里采用同步,同时会判断是否已存在
* @param dbkey
* @param ip
* @param port
* @param service 实例名
* @param username
* @param password
* @return String 新建数据源对应的key,如果已经存在,则返回之前的key。
*/
public synchronized String addDataSource(String dbkey, String ip,
int port, String service, String username, String password){
DataSourceEntity d1 = new DataSourceEntity(ip, port, service, username);
String value = dseMap.get(d1);
if(dseMap.get(d1) != null){
return value;//已存在则返回该数据源的id
} DataSource ds = createDataSource(ip, port, service, username, password);
dataSourceMap.put(dbkey, ds);//存储数据源集合
dseMap.put(d1, dbkey);//保存已经存储了哪些数据源 return dbkey;
} /**
* 创建一个数据源
* @param ip
* @param port
* @param service
* @param username
* @param password
* @return
*/
private DataSource createDataSource(String ip, int port, String service, String username, String password){
DruidDataSource dds = new DruidDataSource();
dds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
dds.setUrl("jdbc:oracle:thin:@"+ip+":"+port+":"+service);
dds.setUsername(username);
dds.setPassword(password);
return dds;
}
2、切换数据源的工具类
public class DataSourceUtils { private static final ThreadLocal<String> local = new ThreadLocal<String>(); public static String getDbKey() {
return local.get();
} public static void setDbKey(String dbKey) {
if(DynamicDatasource.checkDbKey(dbKey)){
local.set(dbKey);
} else {
throw new NullPointerException("不存在id为\""+dbKey+"\"的数据源!");
}
} public static void clear() {
local.remove();
}
}
客户端测试类
@Test
public void Test(){
System.out.println("ok");
//增加数据源
dynamicDatasource.addDataSource("dataSource", "***.***.**.***", ****, "***", "***", "*****");
//指定使用的数据源
DataSourceUtils.setDbKey("dataSource");
//执行sql
int i = (Integer) jdbcTemplate.queryForObject("select count(*) from tablename", Integer.class);
System.out.println("i="+i);
}
以上代码是经过测试后可以直接运行的,由于本人能力有限,综合考虑后对spring中动态创建数据源的实现如上。欢迎各位指点可以优化改进的地方。
spring动态创建数据源的更多相关文章
- spring 动态创建数据源
项目需求如下,公司对外提供服务,公司本身有个主库,另外公司会为每个新客户创建一个数据库,客户的数据库地址,用户名,密码,都保存在主数据库中.由于不断有新的客户加入,所以要求,项目根据主数据库中的信息, ...
- Spring 动态创建并切换数据源
公司要求后端项目可以进行动态创建并切换数据源,看了网上很多例子大多数使用的都是Spring内置的AbstractRoutingDataSource进行的,使用此方法不是不行但是有诸多缺陷,比如切换时需 ...
- Spring动态切换数据源及事务
前段时间花了几天来解决公司框架ssm上事务问题.如果不动态切换数据源话,直接使用spring的事务配置,是完全没有问题的.由于框架用于各个项目的快速搭建,少去配置各个数据源配置xml文件等.采用了动态 ...
- spring动态切换数据源(一)
介绍下spring数据源连接的源码类:| 1 spring动态切换连接池需要类AbstractRoutingDataSource的源码 2 /* 3 * Copyright 2002-2017 the ...
- Spring 动态多数据源
spring springmvc mybatis 多数据源配置时的重点: 1. 注意事务拦截器的配置 Spring中的事务管理与数据源是绑定的,一旦程序执行到Service层(事务管理)的话,由于在进 ...
- Spring动态切换数据源
11 //定义数据源枚举public enum DataSourceKey { master, slave, } 22 /** * 数据源路由 */ @Slf4j public class Dynam ...
- 一文读懂Spring动态配置多数据源---源码详细分析
Spring动态多数据源源码分析及解读 一.为什么要研究Spring动态多数据源 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式 ...
- 动态添加数据源,根据用户登录切换数据库.编程式Spring事务.
根据用户注册,系统自动创建私有数据库,用户登录,动态添加数据源到Spring数据路由,Session超时删除数据源 好处:当数据量大的时候,类似水平切割效果,效率会高一些 坏处:数据源切换,Sprin ...
- Spring动态数据源实现读写分离
一.创建基于ThreadLocal的动态数据源容器,保证数据源的线程安全性 package com.bounter.mybatis.extension; /** * 基于ThreadLocal实现的动 ...
随机推荐
- Web Audio初步介绍和实践
Web Audio还是一个比较新的JavaScript API,它和HTML5中的<audio>是不同的,简单来说,<audio>标签是为了能在网页中嵌入音频文件,和播放器一样 ...
- 面向对象的多态性(C++)
以C++为例三大特效:封装.继承.多态,面向对象的编程语言都具有这些特性. 那么本节来谈谈多态性,尽量说的简单些容易理解! 多态什么意思?即运行时多态,以相同的方式处理不同类型的对象,产生不同的结果! ...
- 转:安装PHP出现make: *** [sapi/cli/php] Error 1 解决办法
ext/iconv/.libs/iconv.o: In function `php_iconv_stream_filter_ctor':/home/king/PHP-5.2.13/ext/iconv/ ...
- CSS小知识-----前缀-moz-、-ms-、-webkit-,-o-分别代表的意思
这种方式在业界上统称:识别码.前缀 -ms-代表[ie]内核识别码 -moz-代表火狐[firefox]内核识别码 -webkit-代表谷歌[chrome]/苹果[safari]内核识别码 -o-代表 ...
- 对象的copy
一般来讲,java中,对象的copy一般是通过Beans.copy(B, A);用来将A对象复制给B对象,包括对象里面的属性和值. 但但但...... 这样做,有一个很严重的问题,那就是:如果把A对象 ...
- JsonPath:从多层嵌套Json中解析所需要的值
问题 应用中,常常要从嵌套的JSON串中解析出所需要的数据.通常的做法是,先将JSON转换成Map, 然后一层层地判空和解析.可使用 JsonPath 来解决这个问题. 给定一个 JSON 串如下所示 ...
- 数据迁移工具Sqoop和DataX功能比较
本文转载自: http://www.cnblogs.com/panfeng412/archive/2013/04/29/data-migration-tool-sqoop-and-datax.html ...
- flask实战-个人博客-使用蓝本模块化程序
使用蓝本模块化程序 实例化flask提供的blueprint类就创建一个蓝本实例.像程序实例一样,我们可以为蓝本实例注册路由.错误处理函数.上下文处理函数,请求处理函数,甚至是单独的静态文件文件夹和模 ...
- Spark With Mongodb 实现方法及error code -5, 6, 13127解决方案
1.spark mongo 读取 val rdd = MongoSpark.builder().sparkSession(spark).pipeline(Seq(`match`(regex(" ...
- flask 请求上下文
一篇引用大牛的 https://www.cnblogs.com/zhaopanpan/p/9457343.html ### 线程安全 ```python# 线程不安全class Foo(object) ...