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

多数据源动态切换

  • 继承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配置文件

 <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。

你可能感兴趣的:(MySQL数据库)