0%

spring配置双数据源

spring配置双数据源

前段时间有个需求,需要将将数据存到两个数据库中,一个库中存放主信息,一个库中存放特殊信息,看来是要使用双数据源了,来搞起来吧

既然是双数据源,先不管怎么切换,配置得先搞起来

数据源配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<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查找数据源的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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,来标识当前方法要使用的数据源

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {

String name() default "video";
}

然后来进行aop的逻辑来根据注解的name属性来存储key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@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;
}
}
}

这就大功告成了,双数据源的配置就搞定了