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

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

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

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

<!--读数据源配置-->
<bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"destroy-method="close">
//配置省略
</bean>

<!--写数据源配置-->
<bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"destroy-method="close">
//配置省略
</bean>

<!-- 动态数据源 -->
<bean id = "dataSource" class="com.xxx.xxx.DynamicDataSource" > 
  <!-- 已配置的数据源 -->   
 <property name="targetDataSources">      
  <map>          
      <entry key="READ" value-ref="readDataSource"/>          
      <entry key="WRITE" value-ref="writeDataSource"/>        
 </map>   
 </property>   
 <!-- 默认的数据源 -->  
 <property name="defaultTargetDataSource" ref="writeDataSource"/>
</bean>
具体的配置省略,建议加载资源文件,注意的点是,如何进行数据源的切换?通过动态数据源的的配置可以看出,其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<String> contextHolder = new ThreadLocal<String>();

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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值