利用Aop实现动态分库分表

利用Aop实现动态分库分表

 

通过继承spring-jdbc提供的AbstractRoutingDataSource抽象类来实现选取DataSource。使用方法就是在bean加载的时候给AbstractRoutingDataSource内部的targetDatasourceMap注入多个数据源。

jdbcTemplate的bean定义如下所示,这样就可以在用到jdbcTemplate的地方进行动态的切换。

@Bean
@Scope("prototype")
public JdbcTemplate jdbcTemplate(DatasourceDynamicRouter datasourceRouter){
  return new JdbcTemplate(datasourceRouter);
}

具体切换到哪个数据源是根据determineCurrentLookupKey这个方法的返回值来决定的。其中DatasourceHolder是根据自己的业务逻辑实现的一个工具类。

public class DatasourceDynamicRouter extends AbstractRoutingDataSource {
   private Logger log = LoggerFactory.getLogger(this.getClass());
   
   @Override
   protected Object determineCurrentLookupKey() {
       String datasourceKey = DatasourceHolder.getDatasourceKey();
       log.debug("determineCurrentLookupKey datasourceKey = {}", datasourceKey);
       return datasourceKey;
  }
   
}

下面附上DatasourceHolder的一个demo

public final class DatasourceHolder {

   private final static Logger log = LoggerFactory.getLogger(DatasourceHolder.class);

   private final static ThreadLocal<String> datasourceKey = new ThreadLocal<>();

   private  static JdbcTemplate jdbcTemplate;
   public static void setDatasourceKey(String key, DataSourceKey dataSourceKey) throws Exception {
       if(dataSourceKey == DataSourceKey.SHARD_KEY){
           //do something
           datasourceKey.set(key);
      }else if(dataSourceKey == DataSourceKey.PRODUCT_NAME){
           //do something
           datasourceKey.set(shardKey);
      }
  }

   public static String getDatasourceKey(){
       return datasourceKey.get();
  }

   public static void clearDatasourceKey(){
       datasourceKey.remove();
  }
}

 

 

 

将标注有DatasourceShardKey的参数作为key值,来选取DataSource

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface DatasourceShardKey {
   String value() default "";
}

 

我们在产品名PRODUCT_NAME上标注上DatasourceShardKey,也就是根据产品名来选择数据库。

@Component
@Aspect
public class MultipleDatasourceAop {
   @Around("execution( * com.zqz.dao.dao..*.*(..))")
   public Object routeAop(ProceedingJoinPoint pjp) throws Throwable {

       String originDsKey = DatasourceHolder.getDatasourceKey();
       Object[] args = pjp.getArgs();
       int dsKeyParamIndex = getDsKeyParamIndex(pjp);

       if(dsKeyParamIndex < 0){
           DatasourceHolder.clearDatasourceKey();
      }else{
           DatasourceHolder.setDatasourceKey(String.valueOf(args[dsKeyParamIndex]), DatasourceHolder.DataSourceKey.PRODUCT_NAME);
      }

       Object proceed = pjp.proceed();
       DatasourceHolder.setDatasourceKey(originDsKey, DatasourceHolder.DataSourceKey.SHARD_KEY);
       return proceed;
  }

//获取标记有DatasourceShardKey的参数的index
   private int getDsKeyParamIndex(ProceedingJoinPoint pjp){
       MethodSignature signature = MethodSignature.class.cast(pjp.getSignature());
       Method method = signature.getMethod();
       Annotation[][] parameterAnnotations = method.getParameterAnnotations();
       int paramsAnnotationLength = parameterAnnotations.length;
       if(paramsAnnotationLength == 0){
           return -1;
      }

       for(int i = 0; i < paramsAnnotationLength; i ++){
           Annotation[] parameterAnnotation = parameterAnnotations[i];
           int length = parameterAnnotation.length;
           if(length == 0){
               continue;
          }
           for(int j = 0 ; j < length ; j ++){
               if(parameterAnnotations[i][j] instanceof DatasourceShardKey){
                   return i;
              }
          }
      }
       return -1;
  }
}

 

 

 

 

解决相互调用的问题:

分库代码使用spring AOP开发, 跟其他Spring AOP技术实现的功能比如@Async, @Transactional 一样, 当在同一个类中函数相互调用的时候,被调用的函数不会被AOP拦截增强。我们列举一个可能出现问题的场景如下:

public Class xxxDao{

 public void bizDao1(@DatasourceShardKey String productName, ...){
   //Do something
   
   // otherProduct != productName
   bizDao2(otherProduct, ...);
}

 public void bizDao2(@DatasourceShardKey String productName, ...){
   //Do something
}
}

如上面逻辑代码所示: xxxDao.bizDao1("Product_1", ...), 但是 在同一个类中 调用了 bizDao2, 查询的产品确实 Product_2的。这个时候 bizDao2 因为不会被 Spring AOP拦截增强,所以 其实 bizDao2还是查询的 Product_1库的东西。

解决方案很多我们列举两种

第一种:使用 exposeProxy 的方法, AopContext.currentProxy()

public Class xxxDao{

 public void bizDao1(@DatasourceShardKey String productName, ...){
   //Do something
   
   // otherProduct != productName
  ((xxxDao)AopContext.currentProxy()).bizDao2(otherProduct, ...);
}

 public void bizDao2(@DatasourceShardKey String productName, ...){
   //Do something
}
}

第二种: 自己注入自己

public Class xxxDao{
 @Autowired
    private xxxDao xxxdao;

 public void bizDao1(@DatasourceShardKey String productName, ...){
   //Do something
   
   // otherProduct != productName
   xxxDao.bizDao2(otherProduct, ...);
}

 public void bizDao2(@DatasourceShardKey String productName, ...){
   //Do something
}
}

 

posted @ 2020-04-16 18:00  一碗正气粥  阅读(831)  评论(0编辑  收藏  举报