spring配置双数据源
前段时间有个需求,需要将将数据存到两个数据库中,一个库中存放主信息,一个库中存放特殊信息,看来是要使用双数据源了,来搞起来吧
既然是双数据源,先不管怎么切换,配置得先搞起来
数据源配置
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="5"/>
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="10"/>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="1"/>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="3000"/>
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="33"/>
<!-- 检测连接是否有效的sql -->
<property name="validationQuery" value="SELECT 1"/>
<!-- 申请连接时执行validationQuery检测连接是否有效 -->
<property name="testOnBorrow" value="false"/>
<!-- 归还连接时执行validationQuery检测连接是否有效 -->
<property name="testOnReturn" value="false"/>
<!-- 申请连接时检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
<property name="testWhileIdle" value="true"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="false"/>
</bean>
<!-- 数据源 -->
<bean name="adDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${adurl}"/>
<property name="username" value="${adusername}"/>
<property name="password" value="${adpassword}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="5"/>
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="10"/>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="1"/>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="3000"/>
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="33"/>
<!-- 检测连接是否有效的sql -->
<property name="validationQuery" value="SELECT 1"/>
<!-- 申请连接时执行validationQuery检测连接是否有效 -->
<property name="testOnBorrow" value="false"/>
<!-- 归还连接时执行validationQuery检测连接是否有效 -->
<property name="testOnReturn" value="false"/>
<!-- 申请连接时检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
<property name="testWhileIdle" value="true"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="false"/>
</bean>
<!-- 动态数据源 -->
<bean id="dynamicDataSource" class="com.zhanghe.webconfig.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="video" value-ref="dataSource"/>
<entry key="ad" value-ref="adDataSource"/>
</map>
</property>
<!-- 默认数据源 -->
<property name="defaultTargetDataSource" ref="dataSource"/>
</bean>
动态数据源
配置好了数据源之后,需要进行定义动态数据源,继承AbstractRoutingDataSource,AbstractRoutingDataSource是基于特定的查找key路由到特定的数据源。它内部维护了一组目标数据源,并且做了路由key与目标数据源之间的映射,提供基于key查找数据源的方法。
public class DynamicDataSource extends AbstractRoutingDataSource {
private final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
// 获取当前数据源key
String key = DataSourceHolder.getCurDataSource();
LOGGER.info("{}线程 获取到的数据源key--->{}",Thread.currentThread().getName(),key);
// 如果没有的话,则使用默认的key
if(StringUtils.isBlank(key)){
key = DataSourceHolder.getDefaultDataSource();
}
LOGGER.info("{}线程 数据源选择--->{}",Thread.currentThread().getName(),key);
return key;
}
}
// 存储当前数据源的key
public class DataSourceHolder {
private static final ThreadLocal<String> CUR_DATA_SOURCE = new ThreadLocal<>();
private static final String DEFAULT_DATA_SOURCE = "video";
public static String getCurDataSource(){
return CUR_DATA_SOURCE.get();
}
public static String getDefaultDataSource(){
return DEFAULT_DATA_SOURCE;
}
public static void setCurDataSource(String dataSource){
if(StringUtils.isNotBlank(dataSource)){
CUR_DATA_SOURCE.set(dataSource);
}
}
public static void clearDataSource(){
CUR_DATA_SOURCE.remove();
}
}
配置是都搞定了,那么怎么切换呢,可以看到在动态数据源中其实是根据key来进行路由获取数据源的,那么其实就是怎么改变这个key了,而且是动态改变,那么就用spring aop来进行解决吧
数据源切换
首先定义一个注解@DataSource,来标识当前方法要使用的数据源
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String name() default "video";
}
然后来进行aop的逻辑来根据注解的name属性来存储key
@Order(1) // 这里要注意一下,由于spring中的@Transactional也是使用的aop来开启事务的,而切换数据源要在开启事务之前,所以我将@Order设置为了1
@Aspect
@Component
public class DataSourceAspect {
private final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
@Pointcut(value="@annotation(com.zhanghe.webconfig.datasource.DataSource)")
public void pointcut(){
}
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint){
String name = getDataSourceName(joinPoint);
LOGGER.info("{}线程拦截切换数据源{}",Thread.currentThread().getName(),name);
DataSourceHolder.setCurDataSource(name);
}
@After(value = "pointcut()")
public void after(){
DataSourceHolder.clearDataSource();
}
/**
* 获取数据源lookupkey
* @param joinPoint
* @return
*/
public String getDataSourceName(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (method != null) {
DataSource dataSource = method.getAnnotation(DataSource.class);
return dataSource.name();
} else {
return null;
}
}
}
这就大功告成了,双数据源的配置就搞定了