spring主从查询AOP切换数据源

之前有个项目最近整理出来共享给大家。

公共查询服务,公司查询场景越来越多,主库压力越来越大,一些非实时数据通过查询丛库数据来减少主库的压力,也防止主库出现故障导致网站不可访问而出现的崩溃。

在一个项目里面配置主从数据源进行查询,考虑最原始的方案就是通过不同的sessionFactory指向不同的数据源进行数据源切换查询。发现如果后面修改接口或者添加接口就需要写双份和改双份,这是非常不方便的。最后发现了spring下面的org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource类:

通过getConnection()方法获取连接-->通过determineTargetDataSource()方法获取DataSource数据源-->通过determineCurrentLookupKey获取数据源在resolvedDataSources这个map里面存在的数据源连接-->resolvedDataSources主要是获取targetDataSources里面的数据源配置

AbstractRoutingDataSource源码如下:

spring主从查询AOP切换数据源

恍然大悟可以通过继承AbstractRoutingDataSource类,重写determineCurrentLookupKey方法获取数据源的key。

下面就改AOP上场了,通过@Around("@annotation(dataSourceSelect)")拦截执行方法上面有@dataSourceSelect注解获取注解里面的值,把值存放到ThreadLocal-->LinkedList里面,查询的时候调用重写后的determineCurrentLookupKey方法获取LinkedList的第一个元素,这样执行查询的时候数据源就完成切换了。@Around最后通过poll清楚LinkedList的头部的值。

spring主从查询AOP切换数据源

spring主从查询AOP切换数据源spring主从查询AOP切换数据源spring主从查询AOP切换数据源

 

附源码:

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>