之前有个项目最近整理出来共享给大家。
公共查询服务,公司查询场景越来越多,主库压力越来越大,一些非实时数据通过查询丛库数据来减少主库的压力,也防止主库出现故障导致网站不可访问而出现的崩溃。
在一个项目里面配置主从数据源进行查询,考虑最原始的方案就是通过不同的sessionFactory指向不同的数据源进行数据源切换查询。发现如果后面修改接口或者添加接口就需要写双份和改双份,这是非常不方便的。最后发现了spring下面的org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource类:
通过getConnection()方法获取连接-->通过determineTargetDataSource()方法获取DataSource数据源-->通过determineCurrentLookupKey获取数据源在resolvedDataSources这个map里面存在的数据源连接-->resolvedDataSources主要是获取targetDataSources里面的数据源配置
AbstractRoutingDataSource源码如下:
恍然大悟可以通过继承AbstractRoutingDataSource类,重写determineCurrentLookupKey方法获取数据源的key。
下面就改AOP上场了,通过@Around("@annotation(dataSourceSelect)")拦截执行方法上面有@dataSourceSelect注解获取注解里面的值,把值存放到ThreadLocal-->LinkedList里面,查询的时候调用重写后的determineCurrentLookupKey方法获取LinkedList的第一个元素,这样执行查询的时候数据源就完成切换了。@Around最后通过poll清楚LinkedList的头部的值。
附源码:
DynamicDataSource.java
import java.util.LinkedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* <b>数据源切换,使用说明:</b><br>
* 新增数据源需要再dataSource.xml里面配置新的数据源<br>
* 在DataSourceKey里面添加一个对应的值<br>
* 查询使用数据源的method上面添加@DataSourceSelect注解并指定DataSourceKey<br>
*
* @ClassName: DynamicDataSource
* @date 2017年6月1日 下午4:07:22
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
private static final ThreadLocal<LinkedList<String>> datasourceHolder = new ThreadLocal<LinkedList<String>>() {
@Override
protected LinkedList<String> initialValue() {
return new LinkedList<String>();
}
};
/**
* 选择使用数据库,并把选择放到当前ThreadLocal的栈顶
*/
public static void useThisDataSource(String dataourceKey) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("use datasource :" + datasourceHolder.get());
}
LinkedList<String> m = datasourceHolder.get();
m.offerFirst(dataourceKey);
}
/**
* 重置当前栈
*/
public static void reset() {
LinkedList<String> m = datasourceHolder.get();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("reset datasource {}", m);
}
if (m.size() > 0) {
m.poll();
}
}
/**
* 返回Key,然后根据key得到对应的数据源 <br>
* AbstractRoutingDataSource
* 第196行得到对应的数据源的key然后通过resolvedDataSources.get(lookupKey)得到对应的数据源<br>
* 默认数据源是主数据源
*/
@Override
protected Object determineCurrentLookupKey() {
LinkedList<String> m = datasourceHolder.get();
String key = m.peekFirst() == null ? DataSourceKey.MASTER : m.peekFirst();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("currenty datasource :" + key);
}
if (null != key) {
return key;
} else {
return null;
}
}
}
DataSourceSelect.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceSelect {
String dataSourceKey() default DataSourceKey.MASTER;//默认是主数据源
}
DataSourceKey.java
/**
* <b>数据库KEY</b>
*/
public class DataSourceKey {
public static final String MASTER = "master";//主数据源
public static final String SLAVE = "slave";//从数据源
}
DataSourceAspect.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* <b>AOP</b>
*
* @ClassName: DataSourceAspect
* @date 2017年6月1日 下午5:00:46
*
*/
@Aspect
@Component
public class DataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
/**
* <b>@annotation(dataSourceSelect)拦截前后执行</b>
*
* @param pjp
* @param dataSourceSelect 必须和@annotation(dataSourceSelect)括号的命名一样<br>
* 否则会出现 0 formal unbound in pointcut 错误
* @return
* @throws Throwable
* @throws RuntimeException
*/
@Around("@annotation(dataSourceSelect)")
public Object doAround(ProceedingJoinPoint pjp, DataSourceSelect dataSourceSelect) {
Object retVal = null;
boolean selectedDataSource = false;
try {
if (null != dataSourceSelect) {
selectedDataSource = true;
//通过@DataSourceSelect里面的dataSourceKey对应的值获取在 DynamicDataSource 里面的数据源
DynamicDataSource.useThisDataSource(dataSourceSelect.dataSourceKey());
}
retVal = pjp.proceed();
} catch (Throwable e) {
logger.error("程序遇到异常:{}",e);
throw new RuntimeException(e);
} finally {
if (selectedDataSource) {
DynamicDataSource.reset();
}
}
return retVal;
}
}
DataSource.xml
<!-- 动态数据源bean配置 -->
<bean id="dynamicDataSource" class="com.xxx.tc.common.utils.datasource.DynamicDataSource">
<!-- 默认数据源 -->
<property name="defaultTargetDataSource" ref="commonQueryDataSource" />
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 新增数据源需要这这里添加对应的数据源的bean,value-ref 指向新增的那个数据源的bean,key需要和DataSourceKey里面的变量名字一致 -->
<entry key="master" value-ref="commonQueryDataSource" />
<entry key="slave" value-ref="slaveCommonQueryDataSource" />
</map>
</property>
</bean>