实现原理
动态数据源切换,就是在程序运行时,更换你获取到的数据源,从而选择读取的数据库(下面举例为自己工程的数据库以及报表程序访问的数据库)。主要使用的技术是:annotation,Spring AOP ,反射。
我们先来看下AbstractRoutingDataSource的定义:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
}
AbstractRoutingDataSource继承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子类。
DataSource 是javax.sql 的数据源接口,定义如下:
public interface DataSource extends CommonDataSource,Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
DataSource 接口定义了2个方法,都是获取数据库连接。我们在看下AbstractRoutingDataSource 如何实现了DataSource接口:
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
很显然就是调用自己的determineTargetDataSource() 方法获取到connection。determineTargetDataSource方法定义如下:
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
主要是下面两段代码获取到数据源:
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
determineCurrentLookupKey方法返回lookupKey,resolvedDataSources方法就是根据lookupKey从Map中获得数据源。resolvedDataSources 和determineCurrentLookupKey定义如下:
private Map<Object, DataSource> resolvedDataSources;
protected abstract Object determineCurrentLookupKey()
看到以上定义,是不是有点思路了,resolvedDataSources是Map类型,我们可以把MasterDataSource和ReportDataSource存到Map中,如下:
key value
master MasterDataSource
Report ReportDataSource
我们可以写一个类DynamicDataSource 继承AbstractRoutingDataSource,实现其determineCurrentLookupKey() 方法,该方法控制返回Map的key,master或 Report。
实现过程
1.在sapplicationContext.xml中设置多个数据数据库的连接信息,Spring默认会去AbstractRoutingDataSource(Bean)获取数据库的Connection。
2.所以使用spring的AbstractRoutingDataSource指定对应的数据库,并使用 defaultTargetDataSource设置默认数据库
3.这个时候根据写好的自定义注解,在对应的Mapper或者service方法上,例如查找方法上增加注解,指定要查找某个数据库(注意一个点,加注解的方法一定要在你的AOP切面表达式中)
4.AOP切面的方法要在对应的spring.xml配置文件中进行初始化,否则aop切面不能正常生效
5.进行测试(注意IOC域问题,AOP配置在与包扫描统一文件中)
DataSource设置
可以使用JDBC、DBCP、alibaba.druid等等任何一种DataSource配置bean
添加到重写的AbstractRoutingDataSource类中(这里是MultipleDataSource)
applicationContext.xml文件:
<!-- report dataSource-->
<bean id="reportDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${reportdb.jndiName}"/>
</bean>
<!-- hap dataSource-->
<bean id="hapDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${db.jndiName}"/>
</bean>
<!--千万注意:这里的dataSource名字一定得叫这个-->
<bean id="dataSource" class="mryx.report.utils.datasource.MultipleDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="hapDataSource" value-ref="hapDataSource"></entry>
<entry key="reportDataSource" value-ref="reportDataSource"></entry>
</map>
</property>
<!-- 默认目标数据源为你主库数据源 -->
<property name="defaultTargetDataSource" ref="hapDataSource"/>
</bean>
<bean id="DataSourceExchange" class="mryx.report.utils.datasource.MultipleDataSourceExchange"/>
<aop:config>
<aop:aspect id="aspect" ref="DataSourceExchange" order="1" >
<!--建议切入到mapper,当然也可以根据不同的execution表达式切入到不同的点-->
<aop:pointcut id="reportDataMgr" expression="execution(* mryx.report..*.*Mapper.*(..))||execution(* hsrp.core.rp.mapper.IGenericMapper.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="reportDataMgr" />
<aop:after method="afterMethod" pointcut-ref="reportDataMgr" />
</aop:aspect>
</aop:config>
注解类:
import java.lang.annotation.*;
/**
* @author jiaqing.xu@hand-china.com
* @date 2018/12/26
* 数据源注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceKey {
String value() default DataSourceKey.master;
String master = "hapDataSource";
String report = "reportDataSource";
}
数据源操作类:
import org.apache.commons.lang3.StringUtils;
/**
* @author jiaqing.xu@hand-china.com
* @date 2018/12/26
* 数据源操作类
*/
public class DynamicDataSourceHolder {
private static ThreadLocal<String> routeKey = new ThreadLocal<String>();
/**
* 获取当前线程的数据源路由的key
*/
public static String getRouteKey() {
String key = routeKey.get();
if (!StringUtils.isNotEmpty(key)) {
return DataSourceKey.master;
}
return key;
}
/**
* 绑定当前线程数据源路由的key
* 使用完成后必须调用removeRouteKey()方法删除
*/
public static void setRouteKey(String key) {
routeKey.set(key);
}
/**
* 删除与当前线程绑定的数据源路由的key
*/
public static void removeRouteKey() {
routeKey.remove();
}
}
获取多数据源类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author jiaqing.xu@hand-china.com
* @date 2018/12/26
* 获得数据源
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(MultipleDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
logger.debug("---------------------当前数据源:---------------{}", DynamicDataSourceHolder.getRouteKey());
return DynamicDataSourceHolder.getRouteKey();
}
}
数据源AOP类:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* @author jiaqing.xu@hand-china.com
* @date 2018/12/26
* 数据源AOP
*/
public class MultipleDataSourceExchange {
/**
* 前置操作
*
* @param joinpoint
*/
public void beforeMethod(JoinPoint joinpoint) {
Class<?> target = joinpoint.getTarget().getClass();
MethodSignature signature = (MethodSignature) joinpoint.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解类
for (Class<?> cls : target.getInterfaces()) {
resetDataSource(cls, signature.getMethod());
}
resetDataSource(target, signature.getMethod());
}
/**
* 后置操作
*
*/
public void afterMethod() {
DynamicDataSourceHolder.removeRouteKey();
}
/**
* 提取目标对象方法注解和类注解中的数据源标识
*/
private void resetDataSource(Class<?> cls, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
// 默认使用类注解
if (cls.isAnnotationPresent(DataSourceKey.class)) {
DataSourceKey source = cls.getAnnotation(DataSourceKey.class);
DynamicDataSourceHolder.setRouteKey(source.value());
}
// 方法注解可以覆盖类注解
Method m = cls.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSourceKey.class)) {
DataSourceKey source = m.getAnnotation(DataSourceKey.class);
DynamicDataSourceHolder.setRouteKey(source.value());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用示意: