使用SpringAOP实现动态数据源读写分离

由于项目越来越大,数据库的压力越来越大,之前只做了简单的数据库主从备份,没有进行读写分离,现在主库的压力越来越明显,除了需要优化SQL之外,减轻主库压力,读写分离是必不可少了,也是项目需要吧,在这里记录一下,也是供大家参考一下吧。

关键词:AOP、AbstractRoutingDataSource、动态切换数据源

废话不多说,首先在XML中进行读、写数据源的配置



//配置省略




//配置省略



 
     
       
            
                
              
    
    
   
 
具体的配置省略,建议加载资源文件,注意的点是,如何进行数据源的切换?通过动态数据源的的配置可以看出,其key就是负责切换数据源的,最最最重要一点:动态数据源 DynamicDataSource这个类一定要继承自:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,重写其determineCurrentLookupKey()方法。

public class DynamicDataSource extends AbstractRoutingDataSource {  
  @Override    
  protected Object determineCurrentLookupKey() {       
     return DataSourceContextHolder.getType();    
    }
}
还需要一个存放key的地方--->> DataSourceContextHolder工具类;为保证切换时线程安全我们使用 ThreadLocal来保存我们的key。

public class DataSourceContextHolder {
    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceContextHolder.class);
    public static final String DATA_SOURCE_WRITE = "WRITE";
    public static final String DATA_SOURCE_READ = "READ";
    // 线程本地环境
    private static final ThreadLocal contextHolder = new ThreadLocal();

    // 设置数据源类型
    public static void setType(String type) {
        if(LOGGER.isDebugEnabled())
            LOGGER.debug("========>>>>>>>切换数据源,类型:"+type+"<<<<<<<<========");
        contextHolder.set(type);
    }
    // 获取数据源类型
    public static String getType() {
        return (contextHolder.get());
    }
    // 清除数据源类型
    public static void clearType() {
        contextHolder.remove();
    }
}
到这里其实现数据源的切换的核心部分已经完成,接下来,就需要实现怎么在特定的场景下去触发数据源切换,那么就交给Spring AOP来完成了。

@Component
@Aspect
public class DynamicDataSourceAspect {

    @Pointcut("execution (* com.x.mapper.*.select*(..)) || execution (* com.x.mapper.*.count*(..)) ")
    public void readMethodPointcut() {}

    @Pointcut("execution(* com.mapper.*.insert*(..))||execution(* com.mapper.*.update*(..))||execution(* com.mapper.*.delete*(..))")
    public void writeMethodPointcut() {}

    @Before("readMethodPointcut()")
    public void switchReadDataSource(){
        //设置读数据源
        DataSourceContextHolder.setType(DataSourceContextHolder.DATA_SOURCE_READ);
    }

    @Before("writeMethodPointcut()")
    public void switchWriteDataSource(){
        //设置写数据源
        DataSourceContextHolder.setType(DataSourceContextHolder.DATA_SOURCE_WRITE);
    }

}

PS:多谢一位朋友提醒,本来我考虑的是切到业务层,自己写个注解来切换,直接切到逆向工程的mapper更省事!

按照之前我的想法,会麻烦一点,思路大体就是,写一个注解@ReadSource,上面的切面切到业务层(或者是直接调用DAO层的逻辑层),对所有读数据的方法加上@ReadSource,切面里面获取方法的注解是否有@ReadSource,如果有则设置读数据源。想想都觉得后怕。。。。项目里几百上千个方法总是有的。。。。!!!

另外,很重要的一点!!!

另外,很重要的一点!!!

另外,很重要的一点!!!

直接切到mapper层面,对一些负责的业务,有可能会导致事务失效,很简单一个例子,一个业务即涉及读也涉及写,过程中发生了异常,由于事务针对同一数据源,很有可能导致回滚失败!!!所以之前想的方法虽然麻烦,但是在这种情况可还是可以避免掉事务失效的!!

两种方法各有利弊吧,看各自具体项目实际需求取舍了。

你可能感兴趣的:(JAVA)