zoukankan      html  css  js  c++  java
  • 利用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
    }
    }

     

  • 相关阅读:
    MySQL存储引擎简介
    MySQL基本架构介绍
    MySQL事务小结
    【术语解释】fat-jar理解
    学习NIO——Selector
    java 实现Map的深复制
    今天也要学一点设计模式呀——观察者模式
    今天也要学一点设计模式呀——代理模式
    java 将String字符串转换为List<Long>类型
    零拷贝
  • 原文地址:https://www.cnblogs.com/ZhengQiZHou/p/12714635.html
Copyright © 2011-2022 走看看