第一章 spring框架构成
第二章 spring容器
第三章 spring配置bean
第四章 bean的继承和依赖
第五章 bean的生命周期
第六章 spring依赖注入
第七章 Spring AOP
第八章 spring事务
第九章 spring事件监听
第十章 web应用使用spring
第十一章 spring注解整理
第十二章 spring事务失效场景
第十三章 spring bean的作用域
第十四章 spring的扩展接口
第十五章 spring引入外部属性文件
第十六章 spring自定义属性编辑器
第十七章 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"/>
<property name="validationQuery" value="SELECT 1"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<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"/>
<property name="validationQuery" value="SELECT 1"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<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;
}
}
}
这就大功告成了,双数据源的配置就搞定了
参考文献