Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据。

Spring配置多数据源的方式和具体使用过程。 
Spring对于多数据源,以数据库表为参照,大体上可以分成两大类情况: 
一是,表级上的跨数据库。即,对于不同的数据库却有相同的表(表名和表结构完全相同)。 
二是,非表级上的跨数据库。即,多个数据源不存在相同的表。

1、根据用户的选择,使用不同的数据源。

2、解决思路锁定:将sessionFactory的属性dataSource设置成不同的数据源,以达到切换数据源的目的。

3、问题产生:因为整个项目用的几乎都是单例模式,当多个用户并发访问数据库的时候,会产生资源争夺的问题。即项目启动时候,所有的bean都被装载到内存,并且每个bean都只有一个对象。正因为只有一个对象,所有的对象属性就如同静态变量(静态变量跟单例很相似,常用静态来实现单例)。整个项目系统的dataSource只有一个,如果很多用户不断的去改变dataSource的值,那必然会出现资源的掠夺问题,造成系统隐患。

4、多资源共享解决思路:同一资源被抢夺的时候,通常有两种做法,a、以时间换空间 b、以空间换时间。

5、线程同步机制就是典型的“以时间换空间”,采用排队稍等的方法,一个个等待,直到前面一个用完,后面的才跟上,多人共用一个变量,用synchronized锁定排队。   

6、“ThreadLocal”就是典型的“以空间换时间”,她可以为每一个人提供一份变量,因此可以同时访问并互不干扰。

7、言归正传:sessionFactory的属性dataSource设置成不用的数据源,首先不能在配置文件中写死,我们必须为她单独写一个类,让她来引用这个类,在这个类中再来判断我们到底要选择哪个数据源。

spring + mybatis 多数据源切换

DbContextHolder.java

 package com.easyway.stage.commons;  

 public class DbContextHolder
{ // ThreadLocal是线程安全的,并且不能在多线程之间共享。
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setDbType(String dbType)
{
contextHolder.set(dbType);
} public static String getDbType()
{
return ((String) contextHolder.get());
} public static void clearDbType()
{
contextHolder.remove();
} }

MultiDataSource.java

 package com.easyway.stage.commons;  

 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  

 public class MultiDataSource extends AbstractRoutingDataSource
{ @Override
protected Object determineCurrentLookupKey()
{
return DbContextHolder.getDbType();
} }
 

Xml代码:

applicationContext.xml

 
 <?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <!-- 数据源 -->
<bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="maxActive" value="20" />
<property name="minIdle" value="1" /> <!-- 配置获取连接等待超时的时间60s -->
<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" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> <!-- 配置监控统计拦截的filters -->
<property name="filters" value="wall,stat,slf4j" /> <!-- 对于长时间不使用的连接强制关闭 -->
<property name="removeAbandoned" value="true" />
<!-- 超过30分钟开始关闭空闲连接 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
</bean>
<bean id="local" parent="parentDataSource" init-method="init" destroy-method="close">
<property name="url" value="${local.jdbc.url}" />
<property name="username" value="${local.jdbc.username}" />
<property name="password" value="${local.jdbc.password}" />
</bean>
<bean id="server" parent="parentDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean> <bean id="dataSource" class="com.autrade.stage.commons.MultiDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="local" key="local"></entry>
<entry value-ref="server" key="server"></entry>
</map>
</property>
<!-- 默认使用server的数据源 -->
<property name="defaultTargetDataSource" ref="server"></property>
</bean> <!-- MyBatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:resources/mybatis/myBatisConfig.xml" />
<property name="mapperLocations" value="classpath:resources/mybatis/mapper/*.xml"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
</bean>
<!-- MyBatis --> <!-- 配置事务管理对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 将所有具有@Transactional注解的Bean自动配置为声明式事务支持 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <!-- 自定义的拦截器 -->
<bean id="methodAdvisor" class="com.easyway.app.interceptor.InjectorManager" /> <aop:config proxy-target-class="true">
<aop:pointcut id="baseMethods"
expression="execution(* com.easyway.app.service..*.*(..))" />
<aop:advisor advice-ref="methodAdvisor" pointcut-ref="baseMethods" />
</aop:config> </beans>

Test.java测试类

 package com.easyway.stage.test;  

 import javax.sql.DataSource;  

 import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import com.easyway.stage.commons.DbContextHolder; public class Test
{ /**
* @param args
*/
public static void main(String[] args)
{
ApplicationContext appContext = new ClassPathXmlApplicationContext("client-beans.xml"); DbContextHolder.setDbType("local");
String res = "resources/mybatis/myBatisConfig.xml";
DataSource datasource = (DataSource) appContext.getBean("dataSource"); SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(datasource);
Resource resource = new FileSystemResource(res);
bean.setConfigLocation(resource);
try
{
SqlSessionFactory sessionfactory = bean.getObject();
SqlSession session = sessionfactory.openSession();
User user = session.selectOne("com.easyway.mybatis.mapper.findOne");
System.out.println(user.getName());
}
catch (Exception e)
{
e.printStackTrace();
} DbContextHolder.setDbType("server");
String res1 = "resources/mybatis/myBatisConfig.xml";
DataSource datasource1 = (DataSource) appContext.getBean("dataSource"); SqlSessionFactoryBean bean1 = new SqlSessionFactoryBean();
bean1.setDataSource(datasource1);
Resource resource1 = new FileSystemResource(res1);
bean1.setConfigLocation(resource1); try
{
SqlSessionFactory sessionfactory = bean.getObject();
SqlSession session = sessionfactory.openSession();
User user = session.selectOne("com.easyway.mybatis.mapper.findOne");
System.out.println(user.getName());
}
catch (Exception e)
{
e.printStackTrace();
} } }
注意:当切换数据源时,需要在service层之外,如果需要在service层中切换非默认数据源,则不能开启事务,而且下次使用时,线程仍然绑定,此时若需要使用默认数据源,则需要显示的手动切换数据源,否则会出现xxx.table doesnt exist的问题。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据。

Spring2.x以后的版本中采用Proxy模式,就是我们在方案中实现一个虚拟的数据源,并且用它来封装数据源选择逻辑,这样就可以有效地将数据源选择逻辑从Client中分离出来。Client提供选择所需的上下文(因为这是Client所知道的),由虚拟的DataSource根据Client提供的上下文来实现数据源的选择。

实现

具体的实现就是,虚拟的DataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey()在其中封装数据源的选择逻辑。

一、动态配置多数据源
1. 数据源的名称常量类:
  1. /**
  2. * 动态配置多数据源
  3. * 数据源的名称常量类
  4. * @author LONGHUI_LUO
  5. *
  6. */
  7. public class DataSourceConst {
  8. public static final String TEST="test";
  9. public static final String USER="User";
  10. }

2. 建立一个获得和设置上下文环境的类,主要负责改变上下文数据源的名称:

 
  1. /**
  2. * 获得和设置上下文环境 主要负责改变上下文数据源的名称
  3. *
  4. * @author LONGHUI_LUO
  5. *
  6. */
  7. public class DataSourceContextHolder {
  8. private static final ThreadLocal contextHolder = new ThreadLocal(); // 线程本地环境
  9. // 设置数据源类型
  10. public static void setDataSourceType(String dataSourceType) {
  11. contextHolder.set(dataSourceType);
  12. }
  13. // 获取数据源类型
  14. public static String getDataSourceType() {
  15. return (String) contextHolder.get();
  16. }
  17. // 清除数据源类型
  18. public static void clearDataSourceType() {
  19. contextHolder.remove();
  20. }
  21. }

3. 建立动态数据源类,注意,这个类必须继承AbstractRoutingDataSource,且实现方法determineCurrentLookupKey,该方法返回一个Object,一般是返回字符串:

 
  1. /**
  2. * 建立动态数据源
  3. *
  4. * @author LONGHUI_LUO
  5. *
  6. */
  7. public class DynamicDataSource extends AbstractRoutingDataSource {
  8. protected Object determineCurrentLookupKey() {
  9. // 在进行DAO操作前,通过上下文环境变量,获得数据源的类型
  10. return DataSourceContextHolder.getDataSourceType();
  11. }
  12. }

4. 编写spring的配置文件配置多个数据源

  1. <!-- 数据源相同的内容 -->
  2. <bean
  3. id="parentDataSource"
  4. class="org.apache.commons.dbcp.BasicDataSource"
  5. destroy-method="close">
  6. <property
  7. name="driverClassName"
  8. value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
  9. <property name="username" value="sa" />
  10. <property name="password" value="net2com" />
  11. </bean>
  1. <!-- start以下配置各个数据源的特性 -->
  2. <bean parent="parentDataSource" id="testDataSource">
  3. <propertynamepropertyname="url" value="jdbc:sqlserver://localhost:1433;databaseName=test" />
  4. </bean>
  5. <bean parent="parentDataSource" id="UserDataSource">
  6. <property
  7. name="url"
  8. value="jdbc:sqlserver://localhost:1433;databaseName=User" />
  9. </bean>
  1. <!-- end 配置各个数据源的特性 -->

5. 编写spring配置文件配置多数据源映射关系

  1. <bean class="com.xxxx.datasouce.DynamicDataSource" id="dataSource">
  2. <property name="targetDataSources">
  3. <map key-type="java.lang.String">
  4. <entry value-ref="testDataSource" key="test"></entry>
  5. <entry value-ref="UserDataSource" key="User"></entry>
  6. </map>
  7. </property>
  8. <property name="defaultTargetDataSource" ref="testDataSource" ></property>
  9. </bean>

在这个配置中第一个property属性配置目标数据源,<map key-type="Java.lang.String">中的key-type必须要和静态键值对照类DataSourceConst中的值的类型相 同;<entry key="User" value-ref="userDataSource"/>中key的值必须要和静态键值对照类中的值相同,如果有多个值,可以配置多个< entry>标签。第二个property属性配置默认的数据源。

动态切换是数据源

  1. DataSourceContextHolder.setDataSourceType(DataSourceConst.TEST);

该方案的优势

       首先,这个方案完全是在spring的框架下解决的,数据源依然配置在spring的配置文件中,sessionFactory依然去配置它的dataSource属性,它甚至都不知道dataSource的改变。唯一不同的是在真正的dataSource与sessionFactory之间增加了一个MultiDataSource。
其次,实现简单,易于维护。这个方案虽然我说了这么多东西,其实都是分析,真正需要我们写的代码就只有MultiDataSource、SpObserver两个类。MultiDataSource类真正要写的只有getDataSource()和getDataSource(sp)两个方法,而SpObserver类更简单了。实现越简单,出错的可能就越小,维护性就越高。
最后,这个方案可以使单数据源与多数据源兼容。这个方案完全不影响BUS和DAO的编写。如果我们的项目在开始之初是单数据源的情况下开发,随着项目的进行,需要变更为多数据源,则只需要修改spring配置,并少量修改MVC层以便在请求中写入需要的数据源名,变更就完成了。如果我们的项目希望改回单数据源,则只需要简单修改配置文件。这样,为我们的项目将增加更多的弹性。

该方案的缺点

       没有能够解决多用户访问单例“sessionFactory”时共享“dataSource”变量,导致产生争抢“dataSource”的结果,本质类似于操作系统中的“生产者消费者”问题。因此当多用户访问时,多数据源可能会导致系统性能下降的后果。

Spring动态切换多数据源解决方案的更多相关文章

  1. Spring动态切换多数据源事务开启后,动态数据源切换失效解决方案

    关于某操作中开启事务后,动态切换数据源机制失效的问题,暂时想到一个取巧的方法,在Spring声明式事务配置中,可对不改变数据库数据的方法采用不支持事务的配置,如下: 对单纯查询数据的操作设置为不支持事 ...

  2. spring动态切换数据源(一)

    介绍下spring数据源连接的源码类:| 1 spring动态切换连接池需要类AbstractRoutingDataSource的源码 2 /* 3 * Copyright 2002-2017 the ...

  3. 一文读懂Spring动态配置多数据源---源码详细分析

    Spring动态多数据源源码分析及解读 一.为什么要研究Spring动态多数据源 ​ 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式 ...

  4. Spring动态配置多数据源

    Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性.而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时 ...

  5. Spring Boot + Mybatis + Druid 动态切换多数据源

    在大型应用程序中,配置主从数据库并使用读写分离是常见的设计模式. 在Spring应用程序中,要实现读写分离,最好不要对现有代码进行改动,而是在底层透明地支持. 这样,就需要我们再一个项目中,配置两个, ...

  6. Spring动态切换数据源及事务

    前段时间花了几天来解决公司框架ssm上事务问题.如果不动态切换数据源话,直接使用spring的事务配置,是完全没有问题的.由于框架用于各个项目的快速搭建,少去配置各个数据源配置xml文件等.采用了动态 ...

  7. SpringBoot 动态切换多数据源

    1. 配置文件application-dev.properties 2. 动态切换数据源核心 A. 数据源注册器 B. 动态数据源适配器 C. 自定义注解 D. 动态数据源切面     E. 数据源路 ...

  8. Spring动态切换数据源

    11 //定义数据源枚举public enum DataSourceKey { master, slave, } 22 /** * 数据源路由 */ @Slf4j public class Dynam ...

  9. Spring AOP动态切换数据源

    现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用Spring AOP来达到切换数据源,查询不同的数据库就可以 ...

随机推荐

  1. scrapy安装教程

    Step 1 •安装Python2.7(32位版本) –https://www.python.org/downloads/release/python-279/ Setp 2 •打开"运行& ...

  2. 判断字符串的后缀.endswith()

    可以用str.endswith('.jpg')来判断字符串是否以jpg结尾,返回True或者False

  3. (CLR-Via-C#) 类型基础

    CLR要求每个类型最终都派生自System.Object Object提供的公共方法: Equals: 如果两个对象具有相同的值,就返回true GetHashCode: 返回对象的哈希码 ToStr ...

  4. (hdu-4280)Island Transport~测试网络流模板速度~要加挂才能过啊

    Problem Description In the vast waters far far away, there are many islands. People are living on th ...

  5. css 宽高自适应的div 元素 如何居中 垂直居中

    在我们 编写css 样式的时候经常会遇见一个问题 那就是一个 宽高未知的元素 要让他 垂直居中如何实现这个呢 下面是我常用的两种方法 上代码 下面的是 结构代码 <div class=" ...

  6. Java数据类型与SQL数据类型的映射

    Java数据类型与SQL数据类型的映射 SQL Data Type Java Data Type char/varchar/longvarchar String numeric/decimal jav ...

  7. [HAOI 2008]硬币购物

    Description 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了tot次.每次带di枚ci硬币,买si的价值的东西.请问每次有多少种付款方法. Input 第一 ...

  8. 计蒜客NOIP2017提高组模拟赛(五)day1-机智的 AmyZhi

    传送门 很水的题目啦QAQ #include<cstdio> #include<cstdlib> #include<algorithm> #include<c ...

  9. 51 nod 1681 公共祖先 (主席树+dfs序)

    1681 公共祖先 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题   有一个庞大的家族,共n人.已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边). 在另 ...

  10. 【BZOJ4034】【HAOI2015】树上操作

    题目请自行查阅传送门. 典型的树剖题,线段树维护操作,记一下子树在线段树内范围即可. 时间复杂度:\( O(m \log^{2} n) \) #include <stdio.h> #def ...