1、背景
在一个工程中很多情况需要使用多数据源,典型的情况是读写库和写库的地址不一样,需要进行读写库和写库的分离。如下图,直观的方法是配置多个数据源,每个数据源对应一个SqlSession
这样存在的问题是由于每个数据源有自己的一套连接方式,导致代码冗余比较多。多数据源问题,归根结底是连接串的地址不一样(DataSource配置不一样),Spring提供了在DataSource层多数据源切换方式
2、Spring多数据源切换实现
Spring提供了AbstractRoutingDataSource类来实现多数据源之间的切换,它里面维护了一个用Map存储的键值对,其中键为DataSource的名称(切换数据源时用到,对应下面的DataSourceKey类型)值为对应的数据源的引用,如下多数据源配置
<bean id="dataSource1" class="org.apache.tomcat.jdbc.pool.DataSource">
<property name="poolProperties">
<property name="dirverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@10.165.111.11:6521:dataBaseName1" />
</property>
<property name="username" value="name1" />
<property name="password" value="123456" />
</bean>
<bean id="dataSource2" class="org.apache.tomcat.jdbc.pool.DataSource">
<property name="poolProperties">
<property name="dirverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@10.165.111.12:6521:dataBaseName2" />
</property>
<property name="username" value="name2" />
<property name="password" value="123456" />
</bean>
<bean id="multiDataSource" class="com.uttp.MultiDataSource">
<property name="defaultTargetDataSource" ref="dataSource1" />
<property name="targetDataSources">
<map>
<entry key="readAndWrite" value-ref="dataSource1" />
<entry key="read" vaule-ref=dataSource2 />
</map>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="multiDataSource" />
</bean>
其中 MultiDataSource继承自AbstractRoutingDataSource,其实现了抽象接口determineCurrentLookupKey()
public class MultiDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocak<String>();
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
}
在上面配置中AbstractRoutingDataSource属性targetDataSource对应的是所有候选数据源,通过determinCurrentLookupKey方法获取当前需要访问数据库的数据源。现在的问题是怎么改变determinCurrentLookupKey这个方法获取数据源的值,通过Spring提供的AOP的功能可以在多数据源之间来回切换。首先需要定义多数据源名称的注解
@Target({ElementType.METHOD, ELEMENTTYPE.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataSource {
public DataSourceKey value() default DataSourceKey.readAndWrite
public enum DataSourceKey {
readAndWrite,read;
}
}
然后,需要对使用@DataSource注解的数据库操作进行拦截,然后修改数据源
@Aspect
@Service
class MultiDataSourceAspect {
@PointCut("@within(DataSource)")
public void pointCut() {
}
@PointCut("@annotation(DataSource)")
public void pointCut1() {
}
@Around("(pointCut()||pointCut1())&&target(obj)")
public Object doAround(ProceedingJoinPoint pjp, Object ojb) throws Throwable {
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
DataSource dataSource = obj.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()).getAnnotation(DataSource.class);
if (dataSource == null) {
dataSource = (DataSource) ojb.getClass().getAnnotation(DataSource.class);
}
if (dataSource == null) {
dataSource = (dataSource) signature.getDeclaringType().getAnnotation(DataSource.class);
}
if(dataSource != null) {
MultiDataSource.setDataSourceKey(dataSource.value().toString());
}
return pjp.proceed();
}
}
当类或者方法中用到@DataSource(DataSourceKey.read)时,则会切换为只读数据库源