背景
出于某些性能上的要求 我们基于当前代码做了读写分离 读写分离规划
方案
我们使用Spring的AbstractRoutingDataSource来做读写分离
该类提供了determineCurrentLookupKey进行db的获取
可以粗略的认为AbstractRoutingDataSource为一个组合的dataSource
而我们可以根据业务来进行dataSource的选择
那么很简单我们可以考虑在特殊的业务下面进行切换 随后db在getConnection时将会使用到真实的dataSource
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
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;
}
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
protected abstract Object determineCurrentLookupKey();
很明显当lookupKey为空可以fallback到默认dataSource
<bean id="dataSource-normal" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/erp"/>
</bean>
<bean id="dataSource-slow" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/erp-slow"/>
</bean>
<bean id="dataSource-report" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/erp-saiku"/>
</bean>
<bean id="dataSource-readOnly" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/erp-readOnly"/>
</bean>
<bean id="dataSource" class="com.air.tqb.aop.DataSourceRouter">
<property name="defaultTargetDataSource" ref="dataSource-normal"/>
<property name="targetDataSources">
<map>
<entry key="normal" value-ref="dataSource-normal"/>
<entry key="slow" value-ref="dataSource-slow"/>
<entry key="report" value-ref="dataSource-report"/>
<entry key="readOnly" value-ref="dataSource-readOnly"/>
</map>
</property>
</bean>
因此我们可以定义多个数据源 这样可以根据对应的key来设置到不同的数据源 当然当没有key返回时将返回normal数据源
可是由于变量共享【DataSourceRouter需要从某个方法获取当前的dataSource类型】那么普通变量必将导致dataSource会被其他线程操作
因此此处考虑threadLocal 源码分析之ThreadLocal
因此很明显方案比较明确
- 定义本地线程变量存储当前db路由key
- 根据某些方法名称或者注解等设置特定的db 路由key
- 根据当前线程变量路由key返回特定的dataSource
这样一个简单的方案就出现了 根据不同的routingkey返回不同的connection
实现
-
定义db类型枚举
public enum DataSourceType { NORMAL("normal"), SLOW("slow"), REPORT("report"),READONLY("readOnly"); DataSourceType(String value) { this.value = value; } private String value; public String getValue() { return value; } }
-
新建本地线程变量
public static String getDataSourceRouting() { return DATASOURCE_ROUTING_TL.get(); } public static void setDataSourceRouting(String routingKey) { DATASOURCE_ROUTING_TL.set(routingKey); }
-
根据方法注解或者名称等设置对应routing key
@Aspect @Component @Order(Ordered.HIGHEST_PRECEDENCE + 2) public class DataSourceRoutingAspect { private static final Log logger = LogFactory.getLog(DataSourceRoutingAspect.class); @Around("execution(public * com.air.tqb.service..*.*(..)) && @annotation(com.air.tqb.annoate.DataSource) && @annotation(dataSource)") public Object setDataSource(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable { DataSourceType dataSourceType = dataSource.value(); try { WxbStatic.setDataSourceRouting(dataSourceType.getValue()); if (logger.isDebugEnabled()) { logger.debug("DataSourceType[" + dataSourceType.getValue() + "] set."); } return joinPoint.proceed(); } finally { WxbStatic.clearDataSourceRouting(); if (logger.isDebugEnabled()) { logger.debug("DataSourceType[" + dataSourceType.getValue() + "] remove."); } } } @Around("execution(public * com.air.tqb.service.report..*.*(..))") public Object setDataSource(ProceedingJoinPoint joinPoint) throws Throwable { DataSourceType dataSourceType = DataSourceType.READONLY; try { WxbStatic.setDataSourceRouting(dataSourceType.getValue()); if (logger.isDebugEnabled()) { logger.debug("report DataSourceType[" + dataSourceType.getValue() + "] set."); } return joinPoint.proceed(); } finally { WxbStatic.clearDataSourceRouting(); if (logger.isDebugEnabled()) { logger.debug("report DataSourceType[" + dataSourceType.getValue() + "] remove."); } } } }
-
根据本地线程变量返回特定的routingKey
public class DataSourceRouter extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return WxbStatic.getDataSourceRouting(); } }
实战
根据某些注解设置不同的数据源 比如
@Override
@DataSource(DataSourceType.READONLY)
public PageResult<CustomerCarVO> getPageCustomerCarList(CustomerCarVO customerCarVO, int curPage) throws Exception {
return super.getPageList("getPageCustomerCarList", "getCountBy", customerCarVO, curPage, AppConstant.MIDPAGESIZE);
}