引言
从8月份到现在,团队一直有一项资源整合迁移的任务。简单来说,原来的产品设计规划不合理,各业务组各做各的,导致各类核心数据分散于各个业务组。8月初资源整合方案落地并开始实施,我们组承担起了管控各类数据的任务,未来各业务组生产出的数据以及各业务组需要的数据都只有我们这一个入口。
由于涉及到不同类的数据,并且这些数据是分散在各个业务组不同项目的数据库中,而且迁移产生的逻辑代码也是临时性,写在不同的项目或写在某一项目中也不合理,所以我想到的是用最初的项目框架搭建一个新的项目,使用多数据源,直接访问各个项目的数据库获取源数据,也不需要其他项目组做什么配合工作,数据交接工作只需要给我们梳理清除各个表的关系即可,后续迁移完新项目也直接停止服务即可。
所以接下来写一篇总结,Spring Cloud框架中如何配置多数据源。其实和Spring Boot项目配置一样,只是Spring Cloud框架数据源切换的切面定义有所不同。
快速开始
1. Maven依赖
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
以上依赖主要是数据库相关的,Spring Cloud相关的依赖就不在这展示了,涉及的依赖包括像nacos注册中心、feign、ribbon等等。
2. 数据库类型定义
首先,把需要访问的所有数据库用一个枚举来定义,下面就用英文1-4表示4个不同的数据库。
其次,提供设置与访问数据源类型的方案,为切面设置不同的数据源使用。
public class DataSourceType {
/**
* 数据库类型
*/
public enum DataBaseType {
/**
* 数据库1
*/
one,
/**
* 数据库2
*/
two,
/**
* 数据库3
*/
three,
/**
* 数据库4
*/
four
}
/**
* 使用ThreadLocal保证线程安全
*/
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<>();
/**
* 往当前线程里设置数据源类型
*
* @param dataBaseType 数据源类型
*/
public static void setDataBaseType(DataBaseType dataBaseType) {
if (dataBaseType == null) {
throw new NullPointerException();
}
TYPE.set(dataBaseType);
}
/**
* 获取数据源类型
*
* @return 返回数据源的类型
*/
public static DataBaseType getDataBaseType() {
//默认使用数据库1
return TYPE.get() == null ? DataBaseType.one : TYPE.get();
}
/**
* 清空数据类型
*/
public static void clearDataBaseType() {
TYPE.remove();
}
}
3. 数据库配置
在配置文件中添加各个数据库的连接地址、用户名和密码等信息。
spring:
datasource:
one:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: ****
username: ****
password: ****
queryTimeout: 10000
two:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: ****
username: ****
password: ****
queryTimeout: 10000
three:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: ****
username: ****
password: ****
queryTimeout: 10000
four:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: ****
username: ****
password: ****
queryTimeout: 10000
4. 多数据源核心配置类
@Configuration
//若扫描不到mapper,添加以下注解配置基础包即可
@MapperScan(basePackages = "XXX", sqlSessionFactoryRef = "SqlSessionFactory")
public class DataSourceConfig {
@Primary
@Bean(name = "oneDataSource")
@ConfigurationProperties(prefix = "spring.datasource.one")
public DataSource getOneDateSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "twoDataSource")
@ConfigurationProperties(prefix = "spring.datasource.two")
public DataSource getTwoDateSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "threeDataSource")
@ConfigurationProperties(prefix = "spring.datasource.three")
public DataSource getThreeDateSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "fourDataSource")
@ConfigurationProperties(prefix = "spring.datasource.four")
public DataSource getFourDateSource() {
return DataSourceBuilder.create().build();
}
/**
* 装载多个数据库源
*
* @param oneDataSource
* @param twoDataSource
* @param threeDataSource
* @param fourDataSource
* @return 返回数据源集合
*/
@Bean(name = "dynamicDataSource")
public DynamicDataSource dataSource(@Qualifier("oneDataSource") DataSource oneDataSource,
@Qualifier("twoDataSource") DataSource twoDataSource,
@Qualifier("threeDataSource") DataSource threeDataSource,
@Qualifier("fourDataSource") DataSource fourDataSource) {
Map<Object, Object> targetDataSource = new HashMap<>(16);
targetDataSource.put(DataSourceType.DataBaseType.one, oneDataSource);
targetDataSource.put(DataSourceType.DataBaseType.two, twoDataSource);
targetDataSource.put(DataSourceType.DataBaseType.three, threeDataSource);
targetDataSource.put(DataSourceType.DataBaseType.four, fourDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
//默认数据源
dataSource.setDefaultTargetDataSource(oneDataSource);
return dataSource;
}
/**
* 装配数据源添加扫描mapper.xml文件的路径位置
*
* @param dynamicDataSource 多数据库源对象
* @return 返回sql会话工厂
*/
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory setSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
// 导入mybatis sql session配置
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
// 指明数据源
sessionFactory.setDataSource(dynamicDataSource);
// 指明mapper.xml位置(配置文件中指明的xml位置会失效用此方式代替,具体原因未知)
// sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/**/*Mapper.xml"));
// 指明实体扫描(多个package用逗号或者分号分隔)
sessionFactory.setTypeAliasesPackage("XXX");
// 导入mybatis配置
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
// 配置打印sql语句
configuration.setLogImpl(StdOutImpl.class);
// 添加分页功能
configuration.addInterceptor(new PaginationInterceptor());
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
/**
* 注入事务管理
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}
5. 数据源切面
核心思想就是AOP,通过访问不同路径的包,做数据源切换。在Spring Cloud框架中,主要是通过不同的feign包来区分使用哪个数据源。
@Aspect
@Component
public class DataSourceAop {
/**
* 设定切面地址在所有调用以下包下的任意类的任意方法或接口时切换数据源成指定数据源
*/
@Before("execution(* com.XXX.feign.impl.one..*(..))")
public void setDataSourceOne() {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.one);
}
@Before("execution(* com.XXX.feign.impl.two..*(..))")
public void setDataSourceTwo() {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.two);
}
@Before("execution(* com.XXX.feign.impl.three..*(..))")
public void setDataSourceThree() {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.three);
}
@Before("execution(* com.XXX.feign.impl.four..*(..))")
public void setDataSourceFour() {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.four);
}
}
以上就是Spring Cloud框架的步骤及核心代码。
总结
从结果来看,当初用一个独立的项目配置多数据源还是非常正确的选择。因为各项目难免有不同的需求仍然在不断开发迭代中,迁移这项任务涉及多个项目本来就很复杂,所以这个多数据源项目还是起到了很大的帮助作用的。