一、配置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注解指定执行的先后顺序,值越小优先级越高。否则事务在前,切换数据源则会失效。也是踩了这个坑才知道。