spring下的数据库主从分离(上)

基于mysql数据库已经做好了主从,提供出主库和从库的链接

1、实现方式

主要思路是重写spring的AbstractRoutingDataSource类,使用ThreadLocal保存数据源信息,使用aop切service的方法动态切换数据源。

先看下配置文件


        
            
                
                
            
        
        
    

MyDataSource是我们重写AbstractRoutingDataSource的determineCurrentLookupKey()方法的类

public class MyDataSource extends AbstractRoutingDataSource {
  @Override
  protected Object determineCurrentLookupKey() {
    return DataSourceHolder.getRoutingKey();
  }
}

dataSource这个bean里面的dataSourceW、dataSourceR是我们配置文件上面配置出的两个数据源的bean。targetDataSources用于运行时通过key获取具体数据源,defaultTargetDataSource必须配置,用于默认或者某些情况下使用,下面要说的事务的获取数据源就依赖这个。然后将该dataSource作为参数用于初始化事务DataSourceTransactionManager和SqlSessionFactoryBean。


        
        
        
        
            
                
                
            
        
    

    
        
    

    
    
        
        
    

    
    
        
    
    

然后是保存数据源的ThreadLocal类

public class DataSourceHolder {

  private static final ThreadLocal routingKey = new ThreadLocal<>();

  public static void setRoutingKey(String key) {
    routingKey.set(key);
  }

  public static String getRoutingKey() {
    return routingKey.get();
  }

  public static void removeRoutingKey() {
    routingKey.remove();
  }

}

有set和get方法用于存放、获取数据源key,remove这个方法也很重要,涉及到后面提到的一个坑。

然后定义一个切点,使用aop动态切入数据源

    

    
    
        
            
            
        
    

这里是切的service包及其子包下的所有类,然后就是重要的切面类了

public class DataSourceAspect {

    private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method targetMethod = signature.getMethod();
        DataSource dataSource = targetMethod.getAnnotation(DataSource.class);
        logger.debug("DataSourceAspect setDataSource targetMethod={}, dataSource={}", targetMethod, dataSource);
        if (dataSource == null) {
            //兼容之前代码,不设置标签的就会被设置为master。
            DynamicDataSourceHolder.putDataSource(DataSourceConstant.MASTER);
        } else {
            if (dataSource.value().equals(DataSourceConstant.MASTER)) {
                DynamicDataSourceHolder.putDataSource(DataSourceConstant.MASTER);
            } else if (dataSource.value().equals(DataSourceConstant.SLAVE)) {
                DynamicDataSourceHolder.putDataSource(DataSourceConstant.SLAVE);
            } else {
                DynamicDataSourceHolder.putDataSource(DataSourceConstant.MASTER);
            }
        }


        Object result = null;

        try {
            result = pjp.proceed();
        } catch (Throwable e) {
            throw e;
        } finally {
            //在处理完切点代码后将ThreadLocal值清掉,线程池复用线程会保留上一个线程设置的值,造成混乱
            //实际上service多层调用时,里层调用结束时就会将线程数据源清空了,回到外层数据源变成默认数据源,所以将一定要走从数据源的单写个方法
            DynamicDataSourceHolder.remove();
        }

        return result;
    }
}

拿到方法的标注数据源的标签后,做一些处理,由于项目一开始没有做主从分离,所以策略上很谨慎,尽量不出问题的配置。
1、如果数据源标签为空没设置的时候,使用写数据源,这样保证安全。
2、使用around代替before切面,因为我们需要在方法结束后将ThreadLocal里面的数据源信息清空,这也是个坑,网上的方法大多数是没有这个步骤的,但是不知道是否还有其他好的办法,可以在评论里面提出。

在相应的service方法上注明数据源,debug看一下就可以使用了

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value() default DataSourceConstant.MASTER;
}
    @Override
    @DataSource(DataSourceConstant.SLAVE)
    public List select() {
        return userDAO.select();
    }

加在service的实现层即可

到这使用配置方面就ok了,下篇介绍下源码和坑。
https://www.jianshu.com/p/f5a234e21d2a


你可能感兴趣的:(spring下的数据库主从分离(上))