spring mvc 多数据源配置(二):利用AOP手动切换

背景

spring mvc 多数据源还有一种实现方式,利用 AOP 进行手动切换。

基本原理是,我们自己定义一个 DataSource 类 DynamicDataSource ,来继承 AbstractRoutingDataSource ,然后在配置文件中向 DynamicDataSource 注入两个数据源,然后通过 AOP 来灵活配置。

配置文件

applicationContext.xml

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
<jee:jndi-lookup id="dataSource" jndi-name="java:jboss/datasources/data"></jee:jndi-lookup>
<jee:jndi-lookup id="dataSource2" jndi-name="java:jboss/datasources/data2"></jee:jndi-lookup>

<bean id="dynamicDataSource" class="com.telehot.tpdev.datasource.DynamicDataSource">
<!-- 通过key-value关联数据源 -->
<property name="targetDataSources">
<map>
<entry value-ref="dataSource" key="dataSource"></entry>
<entry value-ref="dataSource2" key="dataSource2"></entry>
</map>
</property>
<!-- 默认数据源 -->
<property name="defaultTargetDataSource" ref="dataSource" />
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dynamicDataSource"></property>
</bean>

<!-- 多数据源切面配置 -->
<bean id="manyDataSourceAspect" class="com.telehot.tpdev.datasource.DataSourceAspect" />
<aop:config>
<aop:aspect id="services" ref="manyDataSourceAspect">
<aop:pointcut id="tx" expression="(execution(* com.telehot..*.services..*.*(..))) or (execution(* com.telehot..*.service..*.*(..)))" />
<aop:before pointcut-ref="tx" method="before" />
<aop:after pointcut-ref="tx" method="after" />
</aop:aspect>
</aop:config>

可以看到,上面配置了两个数据源,分别是 dataSource , dataSource2 。配置了一个自定义的 dynamicDataSource 类,注入了这两个数据源。最后配置了一个 AOP 切面 manyDataSourceAspect 。对 services 下面的类增加了切点。

DynamicDataSource.java

1
2
3
4
5
6
7
8
public class DynamicDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.getDbType();
}

}

这个 DynamicDataSource 继承自 AbstractRoutingDataSource ,重写了 determineCurrentLookupKey , 默认使用了第一个数据源。

DBContextHolder.java

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
public class DBContextHolder {
/**
* @Fields contextHolder : 线程threadlocal
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

/**
* 获取数据库类型
*/
public static String getDbType() {
String db = CONTEXT_HOLDER.get();
if (db == null) {
// 默认是第一个数据源
db = DB_TYPE_DEFAULT;
}
return db;
}

/**
* 设置本线程的dbtype
*/
public static void setDbType(String str) {
CONTEXT_HOLDER.set(str);
}

/**
* 清理连接类型
*/
public static void clearDBType() {
CONTEXT_HOLDER.remove();
}
}

DBContextHolder 类提供了三个方法,获取数据源,设置数据源,清理数据源。

DataSourceType.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSourceType {

/**
* @Fields DB_TYPE_DEFAULT : 第一个数据源字符串
*/
String DB_TYPE_DEFAULT = "dataSource";

/**
* @Fields dataSource2 : 第二个数据源字符串
*/
String DB_TYPE_SECOND= "dataSource2";

String value();
}

增加一个接口,可以通过注解方式切换数据源。

DataSourceAspect.java

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
public class DataSourceAspect {

/**
* 多数据源配置-进入方法切换数据源
*/
public void before(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class< ? > classz = target.getClass();
Class< ? >[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
try {
Method m = classz.getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSourceType.class)) {
DataSourceType data = m.getAnnotation(DataSourceType.class);
DBContextHolder.setDbType(data.value());
}

} catch (Exception e) {
}
}

/**
* 多数据源配置-退出方法清空数据源
*/

public void after(JoinPoint point) {
DBContextHolder.clearDBType();
}
}

这是一个切面,在进入方法前,设置数据源,退出方法后,清空数据源。

使用

在需要手动切换数据源的方法上,加上注解。

1
@DataSourceType(DataSourceType.DB_TYPE_SECOND)

也可以手动加入代码,在指定位置切换数据源。

1
2
3
DBContextHolder.setDbType(DB_TYPE_SECOND);
Integer count = jdbcTemplate.queryForObject("select count(*) from th_demo_field",Integer.class);
System.out.println(count);

由于增加了切面,方法结束后就会清理数据源,因此不会对其它地方产生影响。