Spring配置多数据源并动态切换数据源

一、配置jdbc.properties属性文件
# MySQL
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
jdbc.mysql.username=root
jdbc.mysql.password=root
 
# MS SQL Server (JTDS)
jdbc.sqlserver.driver=net.sourceforge.jtds.jdbc.Driver
jdbc.sqlserver.url=jdbc:jtds:sqlserver://127.0.0.1:1433/test
jdbc.sqlserver.username=root
jdbc.sqlserver.password=root

# Oracle
jdbc.oracle.driver=oracle.jdbc.driver.OracleDriver
jdbc.oracle.url=jdbc:oracle:thin:@127.0.0.1:1521:test
jdbc.oracle.username=root
jdbc.oracle.password=root

# 通用配置
jdbc.initialSize=5
jdbc.minIdle=5
jdbc.maxIdle=20
jdbc.maxActive=100
jdbc.maxWait=100000
二、添加动态数据源切换的类
package com.mi.core;

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

/**
 * 动态切换数据源
 * @author 
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

	public static final String ORACLE_DATA_SOURCE = "oracleDataSource";

	public static final String SQL_SERVER_DATA_SOURCE = "sqlServerDataSource";

	// 本地线程,获取当前正在执行的currentThread
	public static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

	public static void setCustomerType(String customerType) {
		contextHolder.set(customerType);
	}

	public static String getCustomerType() {
		return contextHolder.get();
	}

	public static void clearCustomerType() {
		contextHolder.remove();//清除数据源
	}

	@Override
	protected Object determineCurrentLookupKey() {
		return getCustomerType();
	}

}
三、配置spring-jdbc.xml
	<util:properties id="jdbc"
			location="classpath:properties/jdbc.properties" />
		
	<bean id="oracleDataSource" class="com.alibaba.druid.pool.DruidDataSource"
		destroy-method="close">
		<property name="driverClassName" value="#{jdbc['jdbc.oracle.driver']}" />
		<property name="url" value="#{jdbc['jdbc.oracle.url']}" />
		<property name="username" value="#{jdbc['jdbc.oracle.username']}" />
		<property name="password" value="#{jdbc['jdbc.oracle.password']}" />
		<!-- initialSize: 初始化连接 -->
		<property name="initialSize" value="#{jdbc['initialSize']}" />
		<!-- maxActive: 最大连接数量 -->
		<property name="maxActive" value="#{jdbc['maxActive']}" />
		<!-- 最大等待时间 -->
		<property name="maxWait" value="60000" />
		<!-- minIdle: 最小空闲连接 -->
		<property name="minIdle" value="20" />
		<!-- 1) Destroy线程会检测连接的间隔时间2) testWhileIdle的判断依据 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
		<property name="testWhileIdle" value="true" />
		<!-- 是否缓存preparedStatement,也就是PSCache -->
		<property name="poolPreparedStatements" value="true" />
		<!-- 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true -->
		<property name="maxOpenPreparedStatements" value="20" />
		<!-- 申请连接时执行validationQuery检测连接是否有效 -->
		<property name="testOnBorrow" value="false" />
		<!-- 归还连接时执行validationQuery检测连接是否有效 -->
		<property name="testOnReturn" value="false" />
		<!-- 属性类型是字符串,通过别名的方式配置扩展插件 -->
		<!-- <property name="filters" value="wall,stat" /> -->
		<property name="proxyFilters">
			<list>
				<ref bean="stat-filter" />
				<ref bean="log-filter" />
				<ref bean="wall-filter" />
			</list>
		</property>
	</bean>
	
	<bean id="sqlServerDataSource" class="com.alibaba.druid.pool.DruidDataSource"
		destroy-method="close">
		<property name="driverClassName" value="#{jdbc['jdbc.sqlserver.driver']}" />
		<property name="url" value="#{jdbc['jdbc.sqlserver.url']}" />
		<property name="username" value="#{jdbc['jdbc.sqlserver.username']}" />
		<property name="password" value="#{jdbc['jdbc.sqlserver.password']}" />
		<!-- initialSize: 初始化连接 -->
		<property name="initialSize" value="#{jdbc['initialSize']}" />
		<!-- maxActive: 最大连接数量 -->
		<property name="maxActive" value="#{jdbc['maxActive']}" />
		<!-- 最大等待时间 -->
		<property name="maxWait" value="60000" />
		<!-- minIdle: 最小空闲连接 -->
		<property name="minIdle" value="20" />
		<!-- 1) Destroy线程会检测连接的间隔时间2) testWhileIdle的判断依据 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
		<property name="testWhileIdle" value="true" />
		<!-- 是否缓存preparedStatement,也就是PSCache -->
		<property name="poolPreparedStatements" value="true" />
		<!-- 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true -->
		<property name="maxOpenPreparedStatements" value="20" />
		<!-- 申请连接时执行validationQuery检测连接是否有效 -->
		<property name="testOnBorrow" value="false" />
		<!-- 归还连接时执行validationQuery检测连接是否有效 -->
		<property name="testOnReturn" value="false" />
		<!-- 属性类型是字符串,通过别名的方式配置扩展插件 -->
		<!-- <property name="filters" value="wall,stat" /> -->
		<property name="proxyFilters">
			<list>
				<ref bean="stat-filter" />
				<ref bean="log-filter" />
				<ref bean="wall-filter" />
			</list>
		</property>
	</bean>
	
	<bean id="dataSource" class="com.mi.core.DynamicDataSource">
         <property name="defaultTargetDataSource" ref="oracleDataSource"/><!-- 设置默认为此mySqlDataSource数据源-->
         <property name="targetDataSources">
             <map key-type="java.lang.String">
                 <entry key="oracleDataSource" value-ref="oracleDataSource"/>
                 <entry key="sqlServerDataSource" value-ref="sqlServerDataSource"/>
             </map>
         </property>
    </bean>

	<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
		<property name="config" ref="wall-config" />
	</bean>

	<!--解决mybatis与druid集成后,wallFilter sql注入异常 -->
	<bean id="wall-config" class="com.alibaba.druid.wall.WallConfig">
		<property name="multiStatementAllow" value="true" />
		<property name="noneBaseStatementAllow" value="true" />
	</bean>

	<!-- 慢SQL记录 -->
	<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
		<!-- 慢sql时间设置,即执行时间大于200毫秒的都是慢sql -->
		<property name="slowSqlMillis" value="200" />
		<property name="logSlowSql" value="true" />
	</bean>

	<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
		<!-- 所有连接相关的日志 -->
		<property name="connectionLogEnabled" value="false" />
		<property name="dataSourceLogEnabled" value="false" />
		<!-- 所有Statement相关的日志 -->
		<property name="statementLogEnabled" value="false" />
		<!-- 是否显示结果集 -->
		<property name="resultSetLogEnabled" value="false" />
		<!-- 是否显示SQL语句 -->
		<property name="statementExecutableSqlLogEnable" value="false" />
	</bean>

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:platform/mybatisconfig.xml" />
	</bean>

	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
		<property name="basePackage" value="com.tcl.hrsp.employee.dao" />
	</bean>

	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
	<tx:annotation-driven transaction-manager="txManager" />
	<aop:aspectj-autoproxy />
	<context:component-scan base-package="com.mi" />
四、Controller层测试使用
public Map<String, Object> cardList() {
		DynamicDataSource.setCustomerType(DynamicDataSource.SQL_SERVER_DATA_SOURCE); // 切换sqlServer数据源
		Map<String, Object> resultMap = hrEmpAttendService.cardList();
		DynamicDataSource.clearCustomerType();  //清除数据源
		DynamicDataSource.setCustomerType(DynamicDataSource.ORACLE_DATA_SOURCE);// 切换回主数据源
		return resultMap;
}

注意:切换数据源一定要在事物开启之前,否则切换数据源会失效。因为事物开启后,数据源就不能随意切换了。不过每次要切换数据源都要写一遍切换数据源的代码,并且如果切换数据源后的业务方法抛错了,可能导致无法及时切换主数据源,影响主数据源的业务方法跟着抛错,如果切换回主数据的代码需写在try…catch的finally代码块中,又太繁杂。建议使用AOP来解决动态切换数据源。

使用AOP动态切换数据源

上面三步,再自定义一个注解

package com.mi.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface DataSource {
	
	String name();

}

定义切面

package com.mi.aop;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.mi.annotations.DataSource;
import com.mi.core.DynamicDataSource;

@Aspect
@Component
@Order(1)
public class DataSourceAop {
	
	/**
	 * 定义切入点,com.mi.service包下所有类的所以方法
	 */
	@Pointcut("execution(* com.mi.service.*.*(..))")
	public void pointcut() {}
	
	/**
	 * 前置通知,在目标方法调用之前执行的通知
	 */
	@Before(value="pointcut()")
	public void before(JoinPoint joinPoint) {
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		Method method = methodSignature.getMethod();
		DataSource annotation = method.getAnnotation(DataSource.class);
		if(annotation == null) {
			return;
		}
		String name = annotation.name();
		if(name != null && !"".equals(name)) {
			DynamicDataSource.setCustomerType(name);
		}
	}
	
	/**
	 * 后置通知,不管目标方法是否发生异常都会执行的通知
	 */
	@After("pointcut()")
    public void after(JoinPoint point) {
        //清理掉当前设置的数据源,返回默认的数据源
		DynamicDataSource.clearCustomerType();
    }

}

切面中必须使用@Order注解指定执行的先后顺序,值越小优先级越高。否则事务在前,切换数据源则会失效。也是踩了这个坑才知道。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值