当我们项目变大后,有时候需要多个数据源,接下来我们讲一种能等动态切换数据源的例子。

盗一下图:

  • 单数据源的场景(一般的Web项目工程这样配置进行处理,就已经比较能够满足我们的业务需求)
  • 多数据源多SessionFactory这样的场景,估计作为刚刚开始想象想处理在使用框架的情况下处理业务,配置多个SessionFactory,然后在Dao层中对于特定的请求,通过特定的SessionFactory即可处理实现这样的业务需求
  • 使用AbstractRoutingDataSource 的实现类,进行灵活的切换,可以通过AOP或者手动编程设置当前的DataSource,不用修改我们编写的对于继承HibernateSupportDao的实现类的修改,这样的编写方式比较好

我们这次讲的是第三种,第二种也可以实现,相对来说也比较简单。

第一步:写AbstractRoutingDataSource的实现类

package com.inspur.tax.common.utils;

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

/**
* @ClassName ThreadLocalRountingDataSource
* @Author caozx
* @Description //TODO $
* @Date $ $
**/
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource { @Override
protected Object determineCurrentLookupKey() {
// 获取数据源
return DataSourceTypeManager.getDataSource();
}
}

第二步:设置动态选择的Datasource,这里用到了ThreadLocal。

package com.inspur.tax.common.utils;

/**
* @ClassName DataSourceTypeManager
* @Author caozx
* @Description //TODO $
* @Date $ $
**/
public class DataSourceTypeManager {
/**
* 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
*/
private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() {
return THREAD_DATA_SOURCE.get();
} public static void setDataSource(String dataSource) {
THREAD_DATA_SOURCE.set(dataSource);
} public static void clear() {
THREAD_DATA_SOURCE.remove();
}
}

第三步:在Spring核心容器中配置配置数据源

<?xml version="1.0" encoding="UTF-8"?>
<!-- wbw 2016.8.22 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd"> <bean id="dataSource_mysql" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close"> <property name="url" value="${dataSource.mysql.url}" />
<property name="username" value="${dataSource.mysql.username}" />
<property name="password" value="${dataSource.mysql.password}" /> <!-- 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j
防御sql注入的filter:wall -->
<property name="filters" value="stat" /> <!-- 最大连接池数量(缺省值:8) -->
<property name="maxActive" value="20" />
<!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时(缺省值:0) -->
<property name="initialSize" value="1" />
<!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000" />
<!-- 最小连接池数量 -->
<property name="minIdle" value="1" /> <!-- 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接
2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 (缺省值:1分钟) -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 连接保持空闲而不被驱逐的最长时间(缺省值:30分钟) -->
<property name="minEvictableIdleTimeMillis" value="300000" /> <!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。(缺省值:false) -->
<property name="testWhileIdle" value="true" />
<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。(缺省值:true) -->
<property name="testOnBorrow" value="false" />
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能(缺省值:false) -->
<property name="testOnReturn" value="false" /> <!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
<property name="validationQuery" value="SELECT 1" /> <!-- 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。(缺省值:fasle) -->
<property name="poolPreparedStatements" value="false" />
<!-- 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100(缺省值:-1) -->
<property name="maxOpenPreparedStatements" value="-1" /> <!-- 在上面的配置中,通常你需要配置url、username、password,maxActive这三项 -->
</bean> <bean id="dataSource_oracle" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close"> <property name="url" value="${dataSource.oracle.url}" />
<property name="username" value="${dataSource.oracle.username}" />
<property name="password" value="${dataSource.oracle.password}" /> <!-- 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j
防御sql注入的filter:wall -->
<property name="filters" value="stat" /> <!-- 最大连接池数量(缺省值:8) -->
<property name="maxActive" value="20" />
<!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时(缺省值:0) -->
<property name="initialSize" value="1" />
<!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000" />
<!-- 最小连接池数量 -->
<property name="minIdle" value="1" /> <!-- 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接
2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 (缺省值:1分钟) -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 连接保持空闲而不被驱逐的最长时间(缺省值:30分钟) -->
<property name="minEvictableIdleTimeMillis" value="300000" /> <!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。(缺省值:false) -->
<property name="testWhileIdle" value="true" />
<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。(缺省值:true) -->
<property name="testOnBorrow" value="false" />
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能(缺省值:false) -->
<property name="testOnReturn" value="false" /> <!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
<property name="validationQuery" value="SELECT 1 FROM DUAL" /> <!-- 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。(缺省值:fasle) -->
<property name="poolPreparedStatements" value="true" />
<!-- 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100(缺省值:-1) -->
<property name="maxOpenPreparedStatements" value="100" /> <!-- 在上面的配置中,通常你需要配置url、username、password,maxActive这三项 -->
</bean> <bean id="dataSource" class="com.inspur.tax.common.utils.ThreadLocalRountingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="dataSource_oracle"/>
<entry key="slave" value-ref="dataSource_mysql"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource_oracle"></property>
</bean>
</beans>

第四步:直接在调用dao层之前使用:

    @Override
public Map<String, String> getYyssqk(String qxswjguandm) {
DataSourceTypeManager.setDataSource("slave");
System.out.println(DataSourceTypeManager.getDataSource());
Map<String,Object> params = new HashMap<String, Object>();
params.put("qxswjguandm", qxswjguandm);
Map<String, String> qnMap = sskjjMapper.getYyssqk(params);
params.remove("tb");
Map<String, String> bnMap = sskjjMapper.getYyssqk(params);
DecimalFormat df = new DecimalFormat(",##0.00");
if(Double.parseDouble(qnMap.get("LJ_YYSR"))==0){

注意代码中的红色部分,这就切换到了slave所对应的数据源。注意在代码最后进行清除,重新设置到默认的数据源。

 DataSourceTypeManager.clear()

进行到这里就实现了动态选择数据源,是不是很简单,有没有觉得有点问题呢?没错,每次选择都得执行代码DataSourceTypeManager.setDataSource("slave"),我们可以用aop的注解的方式哟,直接在方法上添加注解(对于方法体内来回切换的那种就老老实实手写吧)。

第五步:写注解

package com.inspur.tax.common.utils;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {
String value() default "";
}

第六步:写切面类,前置通知和后置通知

package com.inspur.tax.common.utils;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* @ClassName DataSourceAspect
* @Author caozx
* @Description //TODO $
* @Date $ $
**/
@Aspect
public class DataSourceAspect { private final static Logger log = LoggerFactory.getLogger(DataSourceAspect.class); @Pointcut("@annotation(com.inspur.tax.common.utils.DynamicSwitchDataSource)")
private void pointCut(){} @Before("pointCut()")
public void beforeMethod(JoinPoint jp){
try {
//获取抽象方法(接口的或抽象类的方法)
Method method = ((MethodSignature) jp.getSignature()).getMethod(); //这个是接口方法的注解,没有所以为null
//DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class); //获取当前类的对象
Class<?> clazz = jp.getTarget().getClass();
//获取实现类的方法
method = clazz.getMethod(method.getName(), method.getParameterTypes());
//获取方法上的注解
DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);
if (annotationClass == null) {
//获取类上面的注解
annotationClass = jp.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);
if (annotationClass == null) return;
}
//获取注解上的数据源的值的信息
String dataSourceKey = annotationClass.value();
if (dataSourceKey != null) { //给当前的执行SQL的操作设置特殊的数据源的信息
DataSourceTypeManager.setDataSource(dataSourceKey);
}
log.info("AOP动态切换数据源");
}catch (Exception e){
log.info(e.getMessage());
}
} @After("pointCut()")
public void after(JoinPoint jp){
DataSourceTypeManager.clear();
log.info("后置通知");
}
}

第七步:调用

    @Override
@DynamicSwitchDataSource("master")
public Map<String, String> getYyssqk(String qxswjguandm) {
System.out.println(DataSourceTypeManager.getDataSource());
Map<String,Object> params = new HashMap<String, Object>();
params.put("qxswjguandm", qxswjguandm);

AbstractRoutingDataSource动态选择数据源的更多相关文章

  1. 【开发笔记】- AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

    AbstractRoutingDataSource动态数据源切换 上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他的业务的需求.一般的情况下我们都是使 ...

  2. AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u012881904/article/de ...

  3. AbstractRoutingDataSource动态数据源切换

    操作数据一般都是在DAO层进行处理,可以选择直接使用JDBC进行编程(http://blog.csdn.net/yanzi1225627/article/details/26950615/) 或者是使 ...

  4. springboot动态多数据源

    参考文章:https://www.cnblogs.com/hehehaha/p/6147096.html 前言 目标是springboot工程支持多个MySQL数据源,在代码层面上,同一个SQL(Ma ...

  5. 基于AbstractRoutingDataSource实现动态切换数据源

    基于AbstractRoutingDataSource实现动态切换数据源 /**  * DataSource注解接口  */ @Target({ElementType.TYPE, ElementTyp ...

  6. Spring3 整合MyBatis3 配置多数据源 动态选择SqlSessionFactory

    一.摘要 上两篇文章分别介绍了Spring3.3 整合 Hibernate3.MyBatis3.2 配置多数据源/动态切换数据源 方法 和 Spring3 整合Hibernate3.5 动态切换Ses ...

  7. Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法

    一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...

  8. hibernate动态切换数据源

    起因: 公司的当前产品,主要是两个项目集成的,一个是java项目,还有一个是php项目,两个项目用的是不同的数据源,但都是mysql数据库,因为java这边的开发工作已经基本完成了,而php那边任务还 ...

  9. spring 动态创建数据源

    项目需求如下,公司对外提供服务,公司本身有个主库,另外公司会为每个新客户创建一个数据库,客户的数据库地址,用户名,密码,都保存在主数据库中.由于不断有新的客户加入,所以要求,项目根据主数据库中的信息, ...

随机推荐

  1. 使用JavaScript制作一个好看的轮播图

    目录 使用JavaScript制作出好看的轮播图效果 准备材料 1.图片若干张(包括轮播图和按钮的图片) 2.将按钮的图片应用到按钮上的CSS样式文件 3.实现轮播和点击跳转的JavaScript代码 ...

  2. mysql服务无法启动(1067错误)时数据备份的经验

    mysql服务无法启动(1067错误)时数据备份的经验 背景 方法 背景 在已安装MySQL5.5的情况下,再次安装 MySQL5.7时,因为MySQL5.7是压缩文件安装的方式,复制MySQL5.5 ...

  3. 洛谷——P2590 [ZJOI2008]树的统计(树链剖分模板练手)

    P2590 [ZJOI2008]树的统计 I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问 ...

  4. 洛谷——P1413 坚果保龄球

    P1413 坚果保龄球 题目描述 PVZ这款游戏中,有一种坚果保龄球.zombie从地图右侧不断出现,向左走,玩家需要从左侧滚动坚果来碾死他们. 我们可以认为地图是一个行数为6,列数为60的棋盘.zo ...

  5. Luogu P1311 选择客栈

    暴力 我一开始做这道题先想到的就是暴力... 所以先说一下暴力的做法.首先在输入的时候讲花费小于P的位置标记下来,然后用两层循环枚举所有的两个客栈的组合方案.再用一层循环将两个客栈之间的位置扫一遍,如 ...

  6. springcloud(十一):熔断聚合监控Hystrix Turbine

    springcloud(十一):熔断聚合监控Hystrix Turbine

  7. Vue2.0 Props双向绑定报错简易处理办法

    在写项目的时候遇到了一个报错问题,虽然功能是正常运行,chrome的提示是:[Vue warn]: Avoid mutating a prop directly since the value wil ...

  8. 关于zookeeper和zkfc的一些测试

    1.停掉zookeeper集群 ****进程影响****** zkfc:报错无法连接zookeeper.ClientCnxn java.net.connectexception:拒绝连接,但不会shu ...

  9. 【01】CSS3 Gradient 分为 linear-gradient(线性渐变)和 radial-gradient(径 向渐变)(转)

    CSS3 Gradient 分为 linear-gradient(线性渐变)和 radial-gradient(径 向渐变).而我们今天主要是针对线性渐变来剖析其具体的用法.为了更好的应用 CSS3 ...

  10. 【Codeforces 682C】Alyona and the Tree

    [链接] 我是链接,点我呀:) [题意] 题意 [题解] 设dis[v]表示v以上的点到达这个点的最大权值(肯定是它的祖先中的某个点到这个点) 类似于最大连续累加和 当往下走(x,y)这条边的时候,设 ...