多数据源动态切换(主备读写分离)

多数据源动态切换

  • 继承Spring-jbdc的AbstractRoutingDataSource,实现其determineCurrentLookupKey方法来确定当前使用的数据源;
  • 一般主从切换针对方法级或类级,需要在执行方法或进入类之前明确当前需要的数据源,这时采用Aop的思想+注解来实现;
  • 为了确保每个线程之间数据源的独立性,需要增加ThreadLocal来隔离。

1、继承AbstractRoutingDataSource

继承AbstractRoutingDataSource,实现其抽象方法determineCurrentLookupKey。

public class MSRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        //ThreadLocal记录当前线程使用的DataSource
        return DataSourceHandler.getDataSource();
    }
}

[外链图片转存失败(img-HP3SxgUl-1563977469384)(media/15633553276487/AbstractRoutingDataSource.png)]

AbstractRoutingDataSource实现jdk的javax.sql.DataSource接口,其DataSource创建Connection时会通过determineCurrentLookupKey方法来确定使用哪一个Datasource。

//1、创建数据库Connection
public Connection getConnection() throws SQLException {
	return determineTargetDataSource().getConnection();
}

//2、确定使用哪一个数据源
protected DataSource determineTargetDataSource() {
	Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
	//lookupKey从Map【resolvedDataSources】中获取对应的Datasource
	Object lookupKey = determineCurrentLookupKey();
	DataSource dataSource = this.resolvedDataSources.get(lookupKey);
	if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
		dataSource = this.resolvedDefaultDataSource;
	}
	if (dataSource == null) {
		throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
	}
	return dataSource;
}

2、记录每个线程的DataSource

在Spring-jdbc确定使用哪一个数据源时,根据当前线程获取对应的DataSource。

public class DataSourceHandler {

    private static ThreadLocal<String> handlerThreadLocal = new ThreadLocal<>();

    /**
     * 提供给AOP去设置当前的线程的数据源的信息
     * @param dataSource
     */
    public static void putDataSource(String dataSource) {
        handlerThreadLocal.set(dataSource);
    }

    /**
     * 提供给AbstractRoutingDataSource的实现类,通过key选择数据源
     * @return
     */
    public static String getDataSource() {
        return handlerThreadLocal.get();
    }

    /**
     * 清空, 使用默认数据源
     */
    public static void remove() {
        handlerThreadLocal.remove();
    }
}

3、多数据源注解

@Target(value = {ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
     * 数据源名称
     * @return
     */
    String value() default "master";
}
  • 使用哪一个数据源切面
//拦截方法或者类上的注解来确定使用哪一个Datasource
@Aspect
@Component
public class DataSourceHandlerAop {
    private static final Logger log = LoggerFactory.getLogger(DataSourceHandlerAop.class);

//需要拦截的注解
@Pointcut("@within(com.xx.provider.datasource.DataSource) || @annotation(com.xx.provider.datasource.DataSource)")
    public void pointcut() {
    }

    /**
     * 在执行具体方法前 确定需要的数据源
     *
     * @param joinPoint
     */
    @Before(value = "pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取方法上的@DataSource注解
        DataSource dataSourceAnnotation = method.getAnnotation(DataSource.class);

        if (dataSourceAnnotation == null) {
            //获取对应类上面的注解
            dataSourceAnnotation = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
            if(dataSourceAnnotation == null) {
                return;
            }
        }

        /**
         * 获取指定的数据源 value,并将数据源保存到当前线程对应的ThreadLocal中,以便后续使用
         */
        String dataSource = dataSourceAnnotation.value();
        if (!StringUtils.isEmpty(dataSource)) {
            DataSourceHandler.putDataSource(dataSource);
        }
        log.info("动态切换数据源,className: {}, methodName: {}, dataSource: {}", joinPoint.getTarget().getClass().getSimpleName(), method.getName(), dataSource);
    }

    /**
     * 方法执行忘后,清空ThreadLocal,不能影响默认数据源
     * @param joinPoint
     */
    @After(value = "pointcut()")
    public void doAfter(JoinPoint joinPoint) {
        DataSourceHandler.remove();
    }
}
  • spring配置文件
<!--数据源,指定每个Datasoure的唯一标识,记在AbstractRoutingDataSource的Map中-->
 <bean id="dataSource" class="com.xx.provider.datasource.MSRoutingDataSource" lazy-init="true">
        <description>主备数据源</description>
        <property name="targetDataSources">
            <map key-type="java.lang.String" value-type="javax.sql.DataSource">
                <entry key="master" value-ref="socialSecurityMasterDS"/>
                <entry key="slave" value-ref="socialSecuritySlaveDS"/>
            </map>
        </property>
        <!-- 设置默认的目标数据源 -->
        <property name="defaultTargetDataSource" ref="socialSecurityMasterDS"/>
    </bean>

如果有很多数据源,那也可以按照这个方式来指定某个方法使用哪一个DataSource。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只打杂的码农

你的鼓励是对我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值