背景
在特征挖掘平台项目中,会遇到在不同的业务线间需要切换不同的数据库去查询原始数据来计算特征,因此需要对多个数据库进行实时切换,这里分享简单的思路及实现方案。
实现思路
利用 Spring
提供的 AbstractRoutingDataSource
抽象出一个 DynamicDataSource
实现动态切换数据源。

实现方案
环境准备
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
| <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.28</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>2.4.5</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.1</version> </dependency>
|
yml
配置
1 2 3 4 5 6 7 8 9 10 11 12
| spring: datasource: one: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 jdbc-url: jdbc:mysql://127.0.0.1:3306/one?useUnicode=true&characterEncoding=UTF-8 two: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 jdbc-url: jdbc:mysql://127.0.0.1:3306/two?useUnicode=true&characterEncoding=UTF-8
|
实现
AOP
切面
1 2 3
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CurrentDataSource {}
|
Aspect
实现
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
| @Aspect @Order(-1) @Component public class CurrentDataSourceAspect {
private static Logger logger = LoggerFactory.getLogger(CurrentDataSourceAspect.class);
@Pointcut("@annotation(com.*.CurrentDataSource)") public void dsPointCut() {}
@Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); List<String> paramNameList = Arrays.asList(signature.getParameterNames()); List<Object> paramList = Arrays.asList(point.getArgs());
CurrentDataSource dataSource = signature.getMethod().getAnnotation(CurrentDataSource.class); String dataSourceValue = DataSourceType.TYPE_ONE.getName(); if (Objects.nonNull(dataSource)) { for (int i = 0; i < paramNameList.size(); i++) { if (paramNameList.get(i).equals("param")) { dataSourceValue = (String) paramList.get(i); logger.info("change datasource: " + dataSourceValue); } } } DataSourceContext.set(DataSourceType.getInstance(dataSourceValue));
try { return point.proceed(); } finally { logger.info("remove datasource: " + dataSourceValue); DataSourceContext.remove(); } }
}
|
DataSourceType
枚举实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Getter @AllArgsConstructor public enum DataSourceType {
TYPE_ONE("a"), TYPE_TWO("b");
private String name;
public static DataSourceType getInstance(String name) { for (DataSourceType type : DataSourceType.values()) { if (name.equals(type.getName())) { return type; } } return DataSourceType.TYPE_ONE; }
}
|
数据源配置类:
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| @Configuration public class DataSourceConfig {
@Bean(name = "dynamicDataSource") @Primary public DataSource dataSource(@Qualifier("oneDataSource") DataSource oneDataSource, @Qualifier("twoDataSource") DataSource twoDataSource) { DynamicDataSource routingDataSource = new DynamicDataSource(); routingDataSource.initDataSource(oneDataSource, twoDataSource); return routingDataSource; }
@Bean(name = "oneDataSource") @ConfigurationProperties(prefix = "spring.datasource.one") public DataSource oneDataSource() { return DataSourceBuilder.create().build(); }
@Bean(name = "twoDataSource") @ConfigurationProperties(prefix = "spring.datasource.two") public DataSource twoDataSource() { return DataSourceBuilder.create().build(); }
@Bean(name = "dynamicSqlSessionFactory") public SqlSessionFactory customSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setTypeAliasesPackage("com.*.mapper"); Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"); bean.setMapperLocations(resources); bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); return bean.getObject(); }
@Bean(name = "dynamicTransactionManager") @Primary public DataSourceTransactionManager customTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
@Bean(name = "dynamicSqlSessionTemplate") @Primary public SqlSessionTemplate customSqlSessionTemplate(@Qualifier("dynamicSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); }
}
|
动态数据源切换配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
@Override protected Object determineCurrentLookupKey() { return DataSourceContext.get(); }
public void initDataSource(DataSource oneDataSource, DataSource twoDataSource) { Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put(DataSourceType.TYPE_ONE, oneDataSource); dataSourceMap.put(DataSourceType.TYPE_TWO, twoDataSource); this.setTargetDataSources(dataSourceMap); this.setDefaultTargetDataSource(oneDataSource); }
}
|
方法上动态切换数据源:
1 2 3 4 5
| @Override @CurrentDataSource public void test() { System.out.println("running"); }
|
引用
个人备注
此博客内容均为作者学习所做笔记,侵删!
若转作其他用途,请注明来源!