Skip to content

Latest commit

 

History

History
171 lines (125 loc) · 5.5 KB

2019-11-12-spring-jdbc-dynamic-DataSource.md

File metadata and controls

171 lines (125 loc) · 5.5 KB
layout title category tags keywords modify description published
post
Spring学习 - 自定义动态数据源
技术
Spring Mysql Java
2019-11-12
学习通过 Spring的 JDBC 封装抽象类实现动态数据源
true

动态数据源

需要实现类继承自org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,来实现自定义的数据源路由。

自定义实现动态数据源

//自定义实现方法`determineCurrentLookupKey`来确定当前lookupKey,通过该 key 找到配置的多个数据源中的目标数据源。
public class DynamicDataSource extends AbstractRoutingDataSource {

    private String defaultDataSourceKey;

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DynamicDataSourceHolder.get();
        String dataSourceKey = StringUtils.isNotEmpty(dataSource) ? dataSource : this.defaultDataSourceKey;
        return dataSourceKey;
    }

    public String getDefaultDataSourceKey() {
        return defaultDataSourceKey;
    }

    public void setDefaultDataSourceKey(String defaultDataSourceKey) {
        this.defaultDataSourceKey = defaultDataSourceKey;
    }
}

lookupkey的动态传递

通过ThreadLocal来实现数据源 lookupkey的动态绑定。如果想要在多线程中使用,则将ThreadLocal换为InheritableThreadLocal

public class DynamicDataSourceHolder {

    private static final ThreadLocal<String> targetDataSource = new ThreadLocal<>();

    public static void set(String target) {
        targetDataSource.set(target);
    }

    public static String get() {
        return targetDataSource.get();
    }

    public static void clear() {
        targetDataSource.remove();
    }
}

lookupkey的设置

通过切面+注解的方式,可以自定义的动态指定不同的 Sql 来访问不同的数据源。

//读主节点的注解
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ReadMaster {

}


//通过切面判断该方法是否读主节点,是的话设置 lookupKey为 masterDataSource
public class AnnotationReadMasterAdvice implements MethodBeforeAdvice, AfterReturningAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        ReadMaster readMaster = method.getAnnotation(ReadMaster.class);
        if (readMaster == null) {
            return;
        }
        DynamicDataSourceHolder.set("masterDataSource")
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        ReadMaster readMaster = method.getAnnotation(ReadMaster.class);
        if (readMaster == null) {
            return;
        }
        DynamicDataSourceHolder.clear();
    }
}

配置使用

   <!-- xml方式切面配置 -->
   <aop:config>
        <aop:pointcut id="serviceMethods"
                      expression="execution(* cn.followtry.dao..*DAO.*(..)) || execution(* cn.followtry.db.dao..*DAO.*(..))"/>
        <aop:advisor advice-ref="annotationReadMasterAdvice" pointcut-ref="serviceMethods"/>
    </aop:config>

   <!-- 实例化自定义的 advice -->
    <bean id="annotationReadMasterAdvice" class="com.meituan.hotel.pegasus.common.db.readstrategy.AnnotationReadMasterAdvice"/>

多数据源设置

通过 Bean 方式实例化动态数据源dynamicDataSource,然后将该动态数据源作为参数配置到jdbcTemplate或者 mybatis 的sqlSessionFactory中,供上层使用。

<bean id="dynamicDataSource"
          class="cn.followtry.db.datasource.DynamicDataSource" primary="true">
   <property name="targetDataSources">
      <map key-type="java.lang.String">
            <entry key="defaultDataSource" value-ref="defaultDataSource"/>
            <entry key="default2DataSource" value-ref="default2DataSource"/>
      </map>
   </property>
   <property name="defaultTargetDataSource" ref="defaultDataSource"/>
   <property name="defaultDataSourceKey" value="defaultDataSource"/>
</bean>

<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
   <property name="dataSource" ref="dynamicDataSource"/>
</bean>

<!-- 集成myBaits框架,配置sqlSessionFatory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="configLocation" value="classpath:dal/mybatis-config.xml"/>
   <property name="dataSource" ref="dynamicDataSource"/>
   <property name="typeAliasesPackage" value="cn.followtry.domain"/>
   <!-- 配置扫描Mapper XML的位置 -->
   <property name="mapperLocations">
      <array>
            <value>classpath*:dal/mappers/*.xml</value>
            <value>classpath*:mybatis/mapper/**/*.xml</value>
            <value>classpath*:mybatis/mapper/*.xml</value>
      </array>
   </property>

</bean>

原理过程

  1. Mybatishibernate等方式配置数据源时将动态数据源配置进去,
  2. 然后在动态数据源dynamicDataSource中配置多个数据源,如defaultDataSource,default2DataSource。在AbstractRoutingDataSource中设置属性resolvedDataSources保存多个数据源,
  3. 在每次实际请求中,通过判断Method上的@ReadMaster注解来将lookupkey设置在上下文DynamicDataSourceHolder中。
  4. 然后每次访问数据源获取连接getConnection时,都通过determineTargetDataSource来检测目标数据源。而目标数据源 key 的指定是通过实现determineCurrentLookupKey方法来完成的。