Springboot-整合动态多数据源

一般在实际开发中,可能需要牵涉到多个数据源,这个时候就需要在使用service方法的时候,动态注入要使用的数据源,下面就简单分析一下如何实现

1 提取数据源的公用属性

一般如下所示
@Configuration
@Slf4j
@Data
public class CommonDataSourceProperties {
    /**
     * 自动提交从池中返回的连接
     */
    @Value("${jdbc.datasource.autoCommit}")
    private boolean autoCommit;

    /**
     * 等待来自池的连接的最大毫秒数
     */
    @Value("${jdbc.datasource.connectionTimeout}")
    private long connectionTimeout;

    /**
     * 接允许在池中闲置的最长时间
     */
    @Value("${jdbc.datasource.idleTimeout}")
    private long idleTimeout;

    /**
     * 池中连接最长生命周期
     */
    @Value("${jdbc.datasource.maxLifetime}")
    private long maxLifetime;

    /**
     * 池中维护的最小空闲连接数
     */
    @Value("${jdbc.datasource.minimumIdle}")
    private int minimumIdle;

    /**
     * 池中最大连接数,包括闲置和使用中的连接
     */
    @Value("${jdbc.datasource.maximumPoolSize}")
    private int maximumPoolSize;

    /**
     * 连接尝试查询
     */
    @Value("${jdbc.datasource.connectionTestQuery}")
    private String connectionTestQuery;


    @PostConstruct
    public void print(){
        log.info("autocommit:"+autoCommit+",connectionTimeout:"+connectionTimeout+",idleTimeout:"+idleTimeout+"....");
    }

}

2 配置多个数据源

通常会把多个数据源存放到一个类里面进行维护
@Configuration
public class DataSourceConfiguration {

    @Autowired
    private CommonDataSourceProperties commonDataSourceProperties;
    /**
     * 数据中心数据源
     * @return
     */
    @Bean(name = "dataCenterDataSource")
    @ConfigurationProperties(prefix="spring.datasource.datacenter")
    public DataSource createDatacenterDataSource() {
        HikariDataSource hikariDataSource = (HikariDataSource) DataSourceBuilder.create().build();
        initDataSourceConfig(hikariDataSource);
        return hikariDataSource;
    }

    /**
     * 前置校验数据源
     * @return
     */
    @Bean(name = "standardDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.standard")
    public DataSource createStandardDataSource() {
        HikariDataSource hikariDataSource = (HikariDataSource) DataSourceBuilder.create().build();
        initDataSourceConfig(hikariDataSource);
        return hikariDataSource;
    }

    /**
     * 创建科创版数据源
     */
    @Bean(name = "kcbDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.kcb")
    public DataSource createKcbDataSource() {
        HikariDataSource hikariDataSource = (HikariDataSource) DataSourceBuilder.create().build();
        initDataSourceConfig(hikariDataSource);
        return hikariDataSource;
    }

    /**
     * 数据源共用基本属性
     * @param dataSource
     * @return
     */
    private void initDataSourceConfig(HikariDataSource dataSource){
        dataSource.setAutoCommit(commonDataSourceProperties.isAutoCommit());
        dataSource.setConnectionTimeout(commonDataSourceProperties.getConnectionTimeout());
        dataSource.setIdleTimeout(commonDataSourceProperties.getIdleTimeout());
        dataSource.setMaxLifetime(commonDataSourceProperties.getMaxLifetime());
        dataSource.setMinimumIdle(commonDataSourceProperties.getMinimumIdle());
        dataSource.setMaximumPoolSize(commonDataSourceProperties.getMaximumPoolSize());
        dataSource.setConnectionTestQuery(commonDataSourceProperties.getConnectionTestQuery());

    }


    /**
     * 创建动态数据源
     * @return
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(createStandardDataSource());
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap<>();
        dsMap.put(DataSourceContextHolder.DATA_CENTER_SOURCE, createDatacenterDataSource());
        dsMap.put(DataSourceContextHolder.DATA_STANDARD_SOURCE, createStandardDataSource());
        dsMap.put(DataSourceContextHolder.DATA_KCB_SOURCE,createKcbDataSource());
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }

    /**
     * 注入动态数据源到事务
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }


    @Bean(name = "dynamicSqlSessionFactory")
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("dynamicDataSource") DataSource eccsDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(eccsDataSource);
        Resource[] checkResources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml");
        bean.setMapperLocations(ArrayUtils.addAll(checkResources));
        bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return bean.getObject();
    }

    @Bean(name = "dynamicSqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("dynamicSqlSessionFactory") SqlSessionFactory sqlSessionFactory)
            throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

3 保存多个数据源的标识,存放到线程上下文中

考虑到线程共享,把标识每种数据源的标识存放到ThreadLocal
public class DataSourceContextHolder {

    public static final String DATA_CENTER_SOURCE ="DATA_CENTER_SOURCE";
    public static final String DATA_STANDARD_SOURCE = "DATA_STANDARD_SOURCE";
    public static final String DATA_KCB_SOURCE = "DATA_KCB_SOURCE";

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

    /**
     * 设置指定的数据源类型
     * @param dbType
     */
    public static void setDbType(String dbType) {
        contextHolder.set(dbType);
    }

    /**
     * 获取指定的数据源类型
     * @return
     */
    public static String getDbType() {
        return ((String) contextHolder.get());
    }

    /**
     * 清除指定的数据源
     */
    public static void clearDbType() {
        contextHolder.remove();
    }

    /**
     * 判断是否包含数据源
     * @param dsId
     * @return
     */
    public static boolean containsDataSource(String dsId) {
        if (null == dsId) {
            return false;
        } else if (!DATA_CENTER_SOURCE.equals(dsId) && !DATA_STANDARD_SOURCE.equals(dsId) && !DATA_KCB_SOURCE.equals(dsId)) {
            return false;
        } else {
            return true;
        }
    }
}

4 实现多数据源的路由接口

通过实现spring里面自带的数据源路由接口,来达到动态路由机制
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDbType();
    }
}

5 实现数据源动态注入到切面

像这种一般和业务无关的代码,都是通过切面的机制进行实现,在执行方法之前进行数据源的注入
,在执行完方法之后,要把指定的数据源再次给清除掉
@Aspect
@Order(-15) //保证该AOP在@Transactional之前执行
@Component
@Log4j2
public class DataSourceAspect {

    /**
     * 进入方法执行之前 先设置指定的数据源
     * @param point
     * @param dataSource
     * @throws Throwable
     */
    @Before(value = "@annotation(dataSource)")
    public void afterReturning(JoinPoint point, DataSource dataSource) throws Throwable {
        // 获取当前的指定的数据源;
        String dsId = dataSource.value();
        if (!DataSourceContextHolder.containsDataSource(dsId)) {
            log.warn("数据源[" + dataSource.value() + "]不存在,使用默认数据源 > " + DataSourceContextHolder.DATA_CENTER_SOURCE);
        } else {
            log.info("使用数据源 > " + dataSource.value());
            DataSourceContextHolder.setDbType(dataSource.value());
        }
    }

    /**
     * 方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
     * @param point
     * @param dataSource
     */
    @After(value = "@annotation(dataSource)")
    public void restoreDataSource(JoinPoint point, DataSource dataSource) {
        log.info("Destory DataSource  > " + dataSource.value() + " >" + point.getSignature());
        DataSourceContextHolder.clearDbType();
    }
}

6 添加数据源的动态注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSource {
    /**
     * 默认返回数据中心的数据源
     * @return
     */
    String value() default  DataSourceContextHolder.DATA_CENTER_SOURCE;
}

注意,数据源的插入一定要在事务之前获取到,否则就会出现事务连接问题了.,事务都是和特定的Connection绑定的,而Connection的获取依赖于DataSource.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值