SaaS系统从0到1搭建,02多数据源 目标:车队SaaS平

目标:车队SaaS平台

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

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

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

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

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

上菜:多多指教~~

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

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

上码:

首先定义下多数据源配置

1
2
3
4
5
6
7
8
9
10
11
12
13
yaml复制代码##多数据源的配置
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

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

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码import java.lang.annotation.*;

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

配置下多数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
typescript复制代码
/**
* 配置多数据源
*/
@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();
}

}

数据库链接配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
scss复制代码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;
}
}

多数据源的支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typescript复制代码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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
less复制代码/**
* 多数据源,切面处理类
*/
@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搭建,下篇继续….

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%