目标:车队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搭建,下篇继续....