spring主从查询AOP切换数据源
之前有个项目最近整理出来共享给大家。
公共查询服务,公司查询场景越来越多,主库压力越来越大,一些非实时数据通过查询丛库数据来减少主库的压力,也防止主库出现故障导致网站不可访问而出现的崩溃。
在一个项目里面配置主从数据源进行查询,考虑最原始的方案就是通过不同的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>