编程知识 cdmana.com

Playing spring with multiple data sources in this way

 

Distributed services , Often a service corresponds to a library , But sometimes a service needs two libraries , This is the time , One service needs to configure two data sources , To support the needs of the business .


Single data source configuration is as follows :

/**
 * Druid Configuration file for 
 *  For monitoring the database SQL
 */
@Configuration
@Slf4j
@RefreshScope
public class DruidConfiguration {

    @Value("${spring.datasource.url}")
    private String dbUrl;
    ………………………………

    @Value("${spring.datasource.username}")
    private String username;

   

    /**
     *  Configure database connection pool 
     * @return
     */
//    @Bean     // Declare it as Bean example 
//    @Primary  // In the same DataSource in , Use the annotated one first DataSource
    public DataSource dataSource(){
        DruidDataSource datasource = new DruidDataSource();

        log.info(">>>>>>>>>>>>> Start configuration druid Connection pool <<<<<<<<<<<<<<<<<<");

        log.info(">>>>>>>>>>>>>>>>>>>> Database connection  : dbUrl<<<<<<<<<<<<<<<<<<<"+dbUrl);

        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        //configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            log.error("druid configuration initialization filter", e);
        }
        datasource.setConnectionProperties(connectionProperties);

        return datasource;
    }

}

Single data source , When the service starts , The data source configuration class will be loaded , take config The data source configuration information in the service is loaded .


Q: Multiple data sources should not be configured with multiple data source classes ?

A: Of course, there are multiple configurations , Multiple data source classes are as follows :

@Configuration
@Slf4j
@RefreshScope
public class SecondConfig {


    @Value("${spring.appprofiledatasource.url}")
    private String dbUrl;
    …………………………………………

    @Value("${spring.appprofiledatasource.username}")
    private String username;


    private DataSource ds = null;

    /**
     *  Configure database connection pool 
     * @return
     */

    public DataSource dataSource(){
        if(ds != null) return ds;
        DruidDataSource datasource = new DruidDataSource();

        log.info(">>>>>>>>>>>>> Start configuration AppProfileDataSource Connection pool <<<<<<<<<<<<<<<<<<");

        log.info(">>>>>>>>>>>>>>>>>>>> Database connection  : dbUrl<<<<<<<<<<<<<<<<<<<"+dbUrl);

        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        //configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            log.error("druid configuration initialization filter", e);
        }
        datasource.setConnectionProperties(connectionProperties);

        ds=datasource;
        return ds;
    }

}

Q: Now there are two data sources , Which one of them loads first ?

A: It's up to the master data source to command

@Configuration
@ConditionalOnClass({DruidConfiguration.class, AppProfileConfig.class})
public class PrimaryDataSource {

    @Autowired
    DruidConfiguration mysql;
    @Autowired
    SecondConfig odps;

    @Bean
    @Primary
    public DataSource dataSource() {
        MultipleDataSource dynamicDataSource = new MultipleDataSource();
        //  Default data source 
        DataSource o = odps.dataSource();
        DataSource m = mysql.dataSource();
        dynamicDataSource.setDefaultTargetDataSource(m);
        //  Configure multiple data sources 
        Map<Object, Object> datasources = new HashMap<>();
        //datasources.put("dataSource", mysql);
        datasources.put("secondDataSource", o);
        dynamicDataSource.setTargetDataSources(datasources);
        return dynamicDataSource;
    }

}

How to ensure that the master data source can command DruidOnfiguration and SecondConfig Well ? It's up to @Primary 了 , Single data source , There was originally @Primary Annotated , But after configuring multiple data sources ,@Primary The annotation is put in the main data source .


Q: The next question is how to switch data sources when using ?

A: Inherit spring Of AbstractRountingDataSource To define your own dynamic data source , In this way, you can dynamically switch data sources of different databases according to your needs .

public class MultipleDataSource extends AbstractRoutingDataSource {
    /**
	 *  Realization AbstractRoutingDataSource Abstract method of , Get the... Associated with the data source key
	 *  Follow up source code can be found , this key Bound to a data source key value , Then used to get the connection 
	 */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSourceType();
    }
}

Relating to a data source key, We are in DataSourceHolder In class ThreadLocal To save the ,

/**
 *  Save the... Of the current thread data source key
 */
public final class DataSourceHolder {

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

    /**
     *  Bind the current thread data source route key
     *  After use , Must call removeRouteKey() Methods to remove 
     */
    public static void setDataSourceType(String dataSourceType) {
        HOLDER.set(dataSourceType);
    }

    /**
     *  Get the... Of the current thread's data source route key
     * @return
     */
    public static String getDataSourceType() {
        return HOLDER.get();
    }

    public static void clearDataSourceType() {
        HOLDER.remove();
    }
}

Q: So here comes the question , data source key, When will it come in ?

A: We need to use spring Of AOP Program to use the data source of the current method before the business logic method runs key Parse data sources from custom annotations on business logic methods key, To add to DataSourceHolder in .

Section class is as follows :

@Aspect
@Order(1)
@Component
public class MyAspect {


    // Specify the custom annotation class to cut 
    @Pointcut("@annotation(cn.aa.common.aspect.TargetDataSource)")
    public void pointcut() {
    }

//    @Before("execution(* cn.lefull.service..*(..))")
    @Before("@annotation(cn.lefull.common.aspect.TargetDataSource)")
    public void before(JoinPoint point) throws Exception {
        TargetDataSource targetDataSource = AnnotationUtils.findAnnotation(((MethodSignature) point.getSignature()).getMethod(), TargetDataSource.class);
        if (Objects.isNull(targetDataSource)) {
            targetDataSource = AnnotationUtils.findAnnotation(point.getClass(), TargetDataSource.class);
            if (Objects.isNull(targetDataSource)) {
                Object proxy = point.getThis();
                Field h = FieldUtils.getDeclaredField(proxy.getClass().getSuperclass(), "h", true);
                AopProxy aopProxy = (AopProxy) h.get(proxy);
                ProxyFactory advised = (ProxyFactory) FieldUtils.readDeclaredField(aopProxy, "advised", true);
                Class<?> targetClass = advised.getProxiedInterfaces()[0];
                targetDataSource = AnnotationUtils.findAnnotation(targetClass, TargetDataSource.class);
            }
        }
        DataSourceHolder.setDataSourceType(Objects.nonNull(targetDataSource) ? targetDataSource.value() : "dataSource");
    }

    @After("pointcut()")
    public void after(JoinPoint point) {
        DataSourceHolder.clearDataSourceType();
    }
}

Custom annotation class :

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String value() default "dataSource";
}

application

// Add custom comments to methods , Value is the data source key
@Override
@TargetDataSource("secondDataSource")
public int updateByPrimaryKey(IdfaInfo idfaInfo) {
     return idfaInfoRepository.updateByPrimaryKey(idfaInfo);
}

When using , Add custom comments to methods , And assign the value as the second data source , And then when the method executes , You can load the second data source .

The above configuration , Faceted annotations can only be loaded on methods , Add to class cannot get data source , Want to add to class , You need to modify the facet class .

 

版权声明
本文为[Wooden pine cat]所创,转载请带上原文链接,感谢

Scroll to Top