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

盗一下图:

  • 单数据源的场景(一般的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. 浅析Oracle中的不等于号

    前几天碰到一个关于Oracle不等于的问题,最后搜索了一下,发现下面资料,拿来跟大家分享一下,需要的朋友可以参考下     关于Oracle中的不等于号: 在Oracle中, <> != ...

  2. (2) GoJS Node简介

    node GoJS提供了非常简单的创建Node节点的方法,可将文本内容.结点形状.背景颜色.边距等属性通过数据绑定[go.Binding]直接绑定到对应的Node数据中. 本文简单介绍Node的创建过 ...

  3. Sublime Text 3 快捷键(转载)

    本文转自:https://segmentfault.com/a/1190000002570753 (欢迎阅读原文,侵删) Sublime Text 3 快捷键精华版 Ctrl+Shift+P:打开命令 ...

  4. JQurey---新尝试

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  5. 整理推荐比较好用的具有书签搜索功能的chrome插件

    平时在整理学习chrome插件的过程中,经常会去试用各种大家推荐的插件.在去年我们的一篇文章:推荐六款好用的书签收藏夹剪藏型管理插件.这里面介绍的是收藏书签的插件.而随着我们使用chrome越来越频繁 ...

  6. LES on MCT

  7. ZOJ 1516 Uncle Tom's Inherited Land

    题目大意: 除去那些作为荷塘的土地块,将剩余的土地希望每次将两块相邻的地一起卖出,最多能卖出多少种这样的由相邻土地 合成的长方形土地块 很明显的二分图问题,但是要考虑如何建模 一个长方形土地总是由相邻 ...

  8. Elasticsearch使用总结

    原文出自:https://www.2cto.com/database/201612/580142.html ELK干货:http://www.cnblogs.com/xing901022/p/4704 ...

  9. hdu 2094拓扑排序map实现记录

    #include<stdio.h> #include<iostream> #include<algorithm> #include<string> #i ...

  10. Eureka集群的那些坑

    今天遇到一个Eureka集群的一个坑. 问题现场类似是这样的:两台Eureka组成的服务注册中心集群,两台服务提供方server1.server2,两个服务调用方client1.client2. 正常 ...