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. assert后面如果是假则程序崩溃

    assert后面如果是假,则程序崩溃.

  2. canvas绘制圆心扇形可组成颜色随机的七色小花

    啊~现在应该还是春天吧.心情一如既往的烦闷呐.最近做了一个canvas的扇形绘制的东西.把它整理出来变成一个适合春天的花朵绘制~沉闷的工作环境已经让我这种有趣的人也变成了无聊鬼怪呢.下次一定想找一个年 ...

  3. springboot AOP全局拦截日志记录

    @Aspect@Component@Slf4jpublic class WebLogAspect { @Pointcut("execution(public * com.we.control ...

  4. hadoop一键安装伪分布式

    hadoop伪分布式和hive在openSUSE中的安装 在git上的路径为:https://github.com/huabingood/hadoop--------/tree/master 各个文件 ...

  5. mysql 从聚合函数group by到sql_mode

    说到group by, 想必大家都不陌生, 就是对查询的数据进行分组,我们可以通过该操作实现一些特殊需求,比如去重. 最近在项目中使用HQL:" from TSjrz where CBh = ...

  6. 基于webpack的React项目搭建(二)

    前言 前面我们已经搭建了基础环境,现在将开发环境更完善一些. devtool 在开发的过程,我们会经常调试,so,为了方便我们在chrome中调试源代码,需要更改webpack.config.js,然 ...

  7. 规约模式(Specification Pattern)

    一.引言 最近在看一个项目的源码时(DDD),对里面的一些设计思想和设计思路有了一些疑问.当看到(Repository层)中使用了 spec.SatisfiedBy() 时,感觉有点懵.于是在项目中搜 ...

  8. [BZOJ]4200: [Noi2015]小园丁与老司机

    Time Limit: 20 Sec  Memory Limit: 512 MBSec  Special Judge Description 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维 ...

  9. HDU 4787 GRE Words Revenge

    Description Now Coach Pang is preparing for the Graduate Record Examinations as George did in 2011. ...

  10. 【集训第四天·继续刷题】之 lgh怒刚ypj

    继续水题,终于完全掌握了伸展树了,好心痛QAQ. 1.codevs1343 蚱蜢 区间最大值查询+单点移动 最大值查询维护一个mx数组就行,单点移动么,先删除在插入 CODE: /* PS: 比较ma ...