当我们项目变大后, 有时候需要多个数据源, 接下来我们讲一种能等动态切换数据源的例子.
盗一下图:
单数据源的场景 (一般的 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);
来源: http://www.bubuko.com/infodetail-3090413.html