最近项目用的数据库要整合成一个,所以把多源数据库切换的写法要清除掉。所以以下记载了多远数据库切换的用法及个人对源码的理解。

框架:Spring+mybatis+vertx,(多源数据库切换的用法不涉及vertx,所以,适用于ssh,sm,ssh...)。

数据库:mysql

两个关键的api:

一:ThreadLocal,

二:AbstractRoutingDataSource。

我一直坚持先先学会使用,在去探究源码和原理。

部分一(实现代码):

以下为实现代码:

DatabaseSource.xml:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="1111111"/>
<property name="password" value="1111111"/>
<property name="connectionProperties" value="com.mysql.jdbc.Driver"/>
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="2"/>
<property name="maxWait" value="60000"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
<property name="filters" value="stat"/>
</bean>
<bean id="manager" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" parent="dataSource">
<property name="url" value="jdbc:mysql://localhost:33308/manager?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true"/>
</bean>
<bean id="lawSh" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" parent="dataSource">
<property name="url" value="jdbc:mysql://localhost:33308/law_hz?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true"/>
</bean> <bean id="multipleDataSource" class="com.rayeye.law.app.dao.base.MultipleDataSource">
<property name="defaultTargetDataSource" ref="lawSh"/>
<property name="targetDataSources">
<map>
<entry key="d_1" value-ref="manager"/>
<entry key="d_0" value-ref="lawSh"/>
</map>
</property>
</bean>
<!-- 定义事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multipleDataSource"/>
</bean>
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="100" />

  

...
以上为数据库的设置,
解说:
dataSource为两个数据库共同的参数设置;
manager和lawsh的url部分单独设置;
multipleDataSource为共其他bean使用的数据库,封装了两个数据库连接,设置默认的数据库连接池:defaultTargetDataSource属性值:lawsh; 部分二:
动态切换部分:
public class MultipleDataSource extends AbstractRoutingDataSource {
public static Logger logger= LoggerFactory.getLogger(MultipleDataSource.class);
private static final ThreadLocal<String> dataSourceKey =new ThreadLocal<String>(); public static void setDataSourceKey(String dataSource) {
logger.debug("数据源切换====="+dataSource);
dataSourceKey.set(dataSource);
}
@Override
protected Object determineCurrentLookupKey() {
logger.debug("dataSource====key:"+dataSourceKey.get());
return dataSourceKey.get();
} public static void setDataSourceByBid(String db) {
if(db.equals(Constant.DB1)){
setDataSourceKey(Constant.DB1) ;
}else{
setDataSourceKey(Constant.DB0) ;
}
}
} @Component
@Aspect
@Order(2)
public class MultipleMailSourceAspectAdvice {
Logger logger =Logger.getLogger(MultipleMailSourceAspectAdvice.class);
@Around("execution(* com.rayeye.law.app.service.*.*(..))")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
logger.info("doAround ==================dataSourse =========");
MultipleDataSource.setDataSourceKey(Constant.DB0); return jp.proceed();
}
} @Component
@Aspect
@Order(3)
public class MultipleManagerSourceAspectAdvice {
Logger logger =Logger.getLogger(MultipleManagerSourceAspectAdvice.class);
@Around("execution(* com.rayeye.law.app.service_manager.DingService.*(..))")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
logger.info("doAround 2 ==================dataSourse =========");
MultipleDataSource.setDataSourceKey(Constant.DB1);
return jp.proceed();
}
}

  

以上为代码部分,
如此,则你的代码可实现指定java类中的方法使用某个数据库连接。 以下为代码及源码解析部分:
关键api一:AbstractRoutingDataSource:
该类的完整路径:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
本来我想先写ThreadLocal,但刚写几行,觉着先写AbstractRoutingDataSource更容易理解,所以将AbstractRoutingDataSource往前排了。
以下为源码部分:
AbstractRoutingDataSource类可以理解为DataSource的路由中介,可以通过它来切换数据库。
在前面的代码中,我重写了AbstractRoutingDataSource类的determineCurrentLookupKey方法,在该方法中切换了数据库。
原因是因为AbstarctRoutingDatraSource以下一段代码(源码):
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
 protected DataSource determineTargetDataSource() {
   Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}

  其中,determineCurrentLookupKey()就是前面代码中重写的方法,在我们重写的方法中指定了使用哪一个数据库,

此处的变量resolvedDataSources(是一个map)里面就存储着我们在配置文件中设置的两个数据库和它们的key值;
下一段源码显示了resolvedDataSources的来源:
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
其中的targetDataSources 就是我们在配置文件中

<property name="targetDataSources">

<map>
    <entry key="d_1" value-ref="manager"/>
<entry key="d_0" value-ref="lawSh"/>
</map>
</property>
它是一个map,里面存储了两个数据库连接池和对应的key;
在for循环中,spring将数据库连接池和对应的key值放到了resolvedDataSources(一个Map)中;
其中的两个方法的源码如下:
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
至此,可以清楚重写方法determineCurrentLookupKey的目的和意义(切换数据库)。
关键api二:ThreadLocal:
该类的完整路径:java.lang.ThreadLocal<T>;
该类的主要作用是:为每个线程创建独立的局部变量副本,线程之间的ThradLocal互不影响(不同线程使用的不同的数据库,互补影响,线程安全)。
以下为源码部分:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

  可以看到,所有的变量都存储在一个静态的hash map中(ThreadLocalMap),而这个变量却保存在thread中,也就是说,每个线程对ThreadLocalMap都持有一个引用(ThreadLocalMap初始化时再ThreadLocal中进行的)。

至此,可以理解ThreadLocal这个类在切换数据库中的作用了(保存每个线程的数据库连接标志,以供使用和切换)。

至此,数据库切换大部分讲完了,还剩下代码里的注解和databaseSource.xml中的  事务管理器的  order属性没讲,这部分涉及事务管理和切面编程,今天有点事,这个部分下次讲。

为什么突然会想起来把数据库切换给写下来,是因为注意到spring和mybatis整合后的:

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.rayeye.bill.api.dao"/>
</bean>

  这个类的属性sqlSessionFactory,

以下为源码:

  /**
* Specifies which {@code SqlSessionFactory} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.

* <p>
* @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
*
* @param sqlSessionFactory
*/
@Deprecated
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}

  可以看见,当配置单个数据库连接的时候,这个属性可以使用,但配置多个数据库连接的时候,这个属性就并不适用了,而且这个类的部分属性好像已经被弃用了。

以上。

end。


												

切换数据库+ThreadLocal+AbstractRoutingDataSource 一的更多相关文章

  1. Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)

    一.缘由 上一篇文章Spring3.3 整合 Hibernate3.MyBatis3.2 配置多数据源/动态切换数据源 方法介绍到了怎么样在Sping.MyBatis.Hibernate整合的应用中动 ...

  2. 【学亮IT手记】mysql创建/查看/切换数据库

    --创建数据库 create database web_test1 CHARACTER set utf8; --切换数据库 use web_test1; --查看当前使用的数据库 select DAT ...

  3. mysql 切换数据库方案

    业务场景 在SAAS模式下,不同的租户需要切换数据库,我们可以使用动态数据源,动态数据源有个问题,就是需要对每一个数据库创建一个连接池,在初始化的时候初始化这些连接池, 如果多台应用服务器的情况,每一 ...

  4. 动态切换数据库(EF框架)

             文章简略:本文测试项目为Silverlight+EF+RIA Service动态切换数据库的问题 通常,Ado.net EntityFramework的数据库连接字符串Connect ...

  5. OsharpNS轻量级.net core快速开发框架简明入门教程-切换数据库(从SqlServer改为MySql)

    OsharpNS轻量级.net core快速开发框架简明入门教程 教程目录 从零开始启动Osharp 1.1. 使用OsharpNS项目模板创建项目 1.2. 配置数据库连接串并启动项目 1.3. O ...

  6. thinkphp 切换数据库

    除了在预先定义数据库连接和实例化的时候指定数据库连接外,我们还可以在模型操作过程中动态的切换数据库,支持切换到相同和不同的数据库类型.用法很简单, 只需要调用Model类的db方法,用法: 常州大理石 ...

  7. SpringBoot Redis切换数据库遇到的坑

    项目不同业务的redis数据存在不同的库中,操作数据需要切换redis库,在网上找了一段代码,确实可以切换数据库.但是使用一段时间后发现部分数据存储的数据库不正确,排查后发现setDatabase是线 ...

  8. Phalcon如何切换数据库《Phalcon入坑指南系列 三》

    本系列目录 一.Phalcon在Windows上安装 <Phalcon入坑指南系列 一> 二.Phalcon入坑必须知道的功能(项目配置.控制器.模型.增.删.改.查) 三.Phalcon ...

  9. Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用

    [参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...

随机推荐

  1. 烂泥:openvpn配置文件详解

    本文由秀依林枫提供友情赞助,首发于烂泥行天下 在上一篇文章<烂泥:ubuntu 14.04搭建OpenVPN服务器>中,我们主要讲解了openvpn的搭建与使用,这篇文章我们来详细介绍下有 ...

  2. 手把手教你玩GDB

    第一部分牛刀小试:启动GDB开始调试 1.       编译带调试信息的可执行程序:用gcc(g++)编译的时候带上-g选项即可 2.       启动GDB开始调试 (1)gdb program   ...

  3. 好压(HaoZip)的命令行模式用法介绍

    好压压缩软件,又叫“2345好压”,是一款国产的优秀压缩软件,目前是免费的,据官网介绍,该软件永久免费.官网地址:http://haozip.2345.com/ 本文主要对该软件的命令行模式用法进行介 ...

  4. VS2013问题与解决方法

    问题: Getting Error "'Microsoft.VisualStudio.Editor.Implementation.EditorPackage' package did not ...

  5. OpenStack 企业私有云的若干需求(2):自动扩展(Auto-scaling) 支持

    本系列会介绍OpenStack 企业私有云的几个需求: 自动扩展(Auto-scaling)支持 多租户和租户隔离 (multi-tenancy and tenancy isolation) 混合云( ...

  6. CF724B. Batch Sort[枚举]

    B. Batch Sort time limit per test 2 seconds memory limit per test 256 megabytes input standard input ...

  7. UVA 11404 Palindromic Subsequence[DP LCS 打印]

    UVA - 11404 Palindromic Subsequence 题意:一个字符串,删去0个或多个字符,输出字典序最小且最长的回文字符串 不要求路径区间DP都可以做 然而要字典序最小 倒过来求L ...

  8. 片元着色器(Fragment Shader)被称为像素着色器(Pixel Shader),但

    片元着色器(Fragment Shader)被称为像素着色器(Pixel Shader),但片元着色器是一个更合适的名字, 因为此时的片元并不是一个真正意义上的像素.

  9. Java中Array.sort()的几种用法(需要初始化要排序的对象)

    ====================================================== 1.Arrays.sort(int[] a) 这种形式是对一个数组的所有元素进行排序,并且 ...

  10. Spring事务管理----声明式:利用TransactionProxyFactoryBean生成事务代理

    通常建议采用声明式事务管理.声明式事务管理的优势非常明显:代码中无需关于关注事务逻辑,让spring声明式事务管理负责事务逻辑,声明式事务管理无需与具体的事务逻辑耦合,可以方便地在不同事务逻辑之间切换 ...