SaaS系统从0到1搭建,02多数据源

1,844 阅读3分钟

目标:车队SaaS平台

车队管理类似的SaaS平台,从0到1,咱继续..

上一篇说到,客观因素,选择的数据库隔离方案,但作为优秀程序猿,必须支持各种临时场景,能够快速相应,不然会被拉取祭天...

那么现在的产品在沟通、在设计,那优秀的程序猿也要同时做好准备,不然怎么保持流弊与神秘~~

好,话说回来,数据隔离方案既然先定业务字段隔离了,那么咱的框架也得支持多数据源吧,万一哪天某个客户他就是要求自己的数据自己单独隔离呢,是吧,这种有可能的吧,比如我们地推人员,想拉个大客户上来,人家有要求,那必须满足。

(我是后半夜Java,在这分享下经验,那些靠copy的搬运作者,不要随意copy到其他平台了,哪天闲的就去投诉)

上菜:多多指教~~

数据库隔离,对后端程序猿来说,那就是多数据源,多数据在网上也能找到一些方案,一些做法,甚至一些踩过的坑,咱汇集过一些,还是有好的代码。

那么咱(粘贴一代码)也要具备下多数据源的功能支持,

上码:

首先定义下多数据源配置

##多数据源的配置
dynamic:
  datasource:
    slave1:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://localhost:3306/yydsCarBase
      username: root
      password: 123456
    slave2:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://localhost:3306/yydsCar1
      username: root
      password: 123456

优美的代码,总有那么几个注解

import java.lang.annotation.*;

/**
 * 多数据源注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    String value() default "";
}

配置下多数据源


/**
 * 配置多数据源
 */
@Configuration
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DynamicDataSourceConfig {
    @Autowired
    private DynamicDataSourceProperties properties;
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }
    @Bean
    public DynamicDataSource dynamicDataSource(DataSourceProperties dataSourceProperties) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(getDynamicDataSource());
        //配置默认的数据源,无论有几个数据源,都要配置一个默认的, 不然跑不起来
        DruidDataSource defaultDataSource = DynamicDataSourceFactory.buildDruidDataSource(dataSourceProperties);
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
        return dynamicDataSource;
    }
    /**
     * 多数据源的其他数据源配置汇总 
     * 基本都是map<key,value>  <数据源名可以按租户名配置,数据库链接信息>
     * @return
     */
    private Map<Object, Object> getDynamicDataSource(){
        Map<String, DataSourceProperties> dataSourcePropertiesMap = properties.getDatasource();
        Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size());
        dataSourcePropertiesMap.forEach((k, v) -> {
            DruidDataSource druidDataSource = DynamicDataSourceFactory.buildDruidDataSource(v);
            targetDataSources.put(k, druidDataSource);
        });
        return targetDataSources;
    }
}

/**
 * 多数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicContextHolder.peek();
    }

}

数据库链接配置

public class DynamicDataSourceFactory {

    /**
     * 创建数据库链接的基本配置,这里如果是不同类型的数据库,需要留意下,比如Oracle、sqlservice啥的
     * @param properties
     * @return
     */
    public static DruidDataSource buildDruidDataSource(DataSourceProperties properties) {
        DruidDataSource druid = new DruidDataSource();
        try {
            druid.setDriverClassName(properties.getDriverClassName());
            druid.setUrl(properties.getUrl());
            druid.setUsername(properties.getUsername());
            druid.setPassword(properties.getPassword());
            druid.setInitialSize(properties.getInitialSize());
            druid.setMaxActive(properties.getMaxActive());
            druid.setMinIdle(properties.getMinIdle());
            druid.setMaxWait(properties.getMaxWait());
            druid.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
            druid.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
            druid.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis());
            druid.setValidationQuery(properties.getValidationQuery());
            druid.setValidationQueryTimeout(properties.getValidationQueryTimeout());
            druid.setTestOnBorrow(properties.isTestOnBorrow());
            druid.setTestOnReturn(properties.isTestOnReturn());
            druid.setPoolPreparedStatements(properties.isPoolPreparedStatements());
            druid.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements());
            druid.setSharePreparedStatements(properties.isSharePreparedStatements());
            druidDataSource.setFilters(properties.getFilters());
            druidDataSource.init();
        } catch (SQLException e) {
            //其实这里还是有点问题,如果捕获异常了,还是返回数据库链接,外头还是链接不上的,Mark下
            e.printStackTrace();
        }
        return druidDataSource;
    }
}

多数据源的支持

public class DynamicContextHolder {
    private static final ThreadLocal<Deque<String>> CONTEXT_HOLDER = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new ArrayDeque();
        }
    };
    /**
     * 获得当前线程数据源
     * @return 数据源名称
     */
    public static String peek() {
        return CONTEXT_HOLDER.get().peek();
    }
    /**
     * 设置当前线程数据源
     * @param dataSource 数据源名称
     */
    public static void push(String dataSource) {
        CONTEXT_HOLDER.get().push(dataSource);
    }
    /**
     * 清空当前线程数据源
     */
    public static void poll() {
        Deque<String> deque = CONTEXT_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            CONTEXT_HOLDER.remove();
        }
    }
}

最后,统一处理AOP

/**
 * 多数据源,切面处理类
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DataSourceAspect {

    @Pointcut("@annotation(io.renren.commons.dynamic.datasource.annotation.DataSource) " +
            "|| @within(io.renren.commons.dynamic.datasource.annotation.DataSource)")
    public void dataSourcePointCut() {
    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class targetClass = point.getTarget().getClass();
        Method method = signature.getMethod();
        DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class);
        DataSource methodDataSource = method.getAnnotation(DataSource.class);
        if(targetDataSource != null || methodDataSource != null){
            String value;
            if(methodDataSource != null){
                value = methodDataSource.value();
            }else {
                value = targetDataSource.value();
            }
            DynamicContextHolder.push(value);
        }
        try {
            return point.proceed();
        } finally {
            DynamicContextHolder.poll();
        }
    }
}

总结

多数据源的处理,网上很多方法或开源的,都可以直接用,只是从概念上或者思想上,需要去了解下他是怎么运作的。 其他其实不去深入到修改底层源码的需要,基本能用就可以了。

SaaS系统从0到1搭建,下篇继续....