SpringBoot动态配置数据源的几种实现方式
一、Apache Druid方式
1、配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&generateSimpleParameterMetadata=true&autoReconnect=true&useSSL=true
username: root
password: root3306
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&generateSimpleParameterMetadata=true&autoReconnect=true&useSSL=true
username: root
password: root3306
druid-pool:
#连接池的最大数据库连接数。设为0表示无限制
max-active: 20
#初始化数量
initial-size: 2
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制
max-wait: 60000
#最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接
min-idle: 2
2、配置数据源
配置数据源基本信息,并将所有数据源添加到动态数据源中进行进行管理
@Configuration
public class DynamicDataSourceConfig {
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Value("${spring.datasource.druid-pool.initial-size}")
private int initialSize;
@Value("${spring.datasource.druid-pool.max-active}")
private int maxActive;
@Value("${spring.datasource.druid-pool.max-wait}")
private int maxWait;
@Value("${spring.datasource.druid-pool.min-idle}")
private int minIdle;
@Primary
@Bean("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
DruidDataSource druidDataSource = (DruidDataSource) DataSourceBuilder.create().type(dataSourceType).build();
druidPoolConfig(druidDataSource);
return druidDataSource;
}
@Bean("slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
DruidDataSource druidDataSource = (DruidDataSource) DataSourceBuilder.create().type(dataSourceType).build();
druidPoolConfig(druidDataSource);
return druidDataSource;
}
private void druidPoolConfig(DruidDataSource druidDataSource) {
druidDataSource.setMaxActive(maxActive);
druidDataSource.setMaxWait(maxWait);
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMinIdle(minIdle);
}
@Bean("dataSource")
@DependsOn({"masterDataSource", "slaveDataSource"})
public DataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("masterDataSource", masterDataSource);
dataSourceMap.put("slaveDataSource", slaveDataSource);
DynamicDataSourceContextHolder.dataSourceNames.add("masterDataSource");
DynamicDataSourceContextHolder.dataSourceNames.add("slaveDataSource");
// 设置动态数据源
return new DynamicDataSource(masterDataSource, dataSourceMap);
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
}
@Override
public Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSource();
}
}
自定义DynamicDataSouce并继承AbstractRoutingDataSource 。AbstractRoutingDataSource 是 Spring 框架中用于实现动态数据源路由的一个抽象类。它通过实现数据源的动态切换来支持多数据源的应用场景。AbstractRoutingDataSource 的核心原理是通过一个线程本地变量(ThreadLocal)来保存当前使用的数据源标识,然后在每次获取连接时,根据这个标识来决定使用哪个数据源。
主要方法包括:
1)determineCurrentLookupKey():这是一个抽象方法,需要子类实现。它用于确定当前使用的数据源标识。根据返回的标识,AbstractRoutingDataSource 会从预先配置的数据源中选择对应的数据源。
2)setTargetDataSources():用于设置数据源的映射关系。这个方法接受一个 Map<Object, Object> 类型的参数,键为数据源标识,值为具体的数据源对象。
3)setDefaultTargetDataSource():用于设置默认的数据源。如果没有找到匹配的数据源标识,就会使用默认的数据源。
4)afterPropertiesSet():检查配置,确保 targetDataSources 和 defaultTargetDataSource 属性已被设置;初始化映射,将指定的默认数据源和目标数据源映射解析并存储到相应的内部变量中。
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> DATASOURCE_CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 管理全部数据源
*/
public static List<String> dataSourceNames = new ArrayList<>();
/**
* 判断是否存在指定数据源
*/
public static boolean containsDataSource(String dataSourceName) {
return dataSourceNames.contains(dataSourceName);
}
/**
* 设置当前数据源
*/
public static void setDataSource(String dataSourceName) {
DATASOURCE_CONTEXT_HOLDER.set(dataSourceName);
}
/**
* 获取当前数据源
*/
public static String getDataSource() {
return DATASOURCE_CONTEXT_HOLDER.get();
}
/**
* 清除数据源
*/
public static void clear() {
DATASOURCE_CONTEXT_HOLDER.remove();
}
}
ThreadLocal是Java中的一个工具类,能够为每个线程提供一个独立的变量副本,从而实现线程隔离。利用ThreadLocal管理当前使用的数据源,DynamicDataSourceContextHolder可以在不同的线程间隔离数据源的使用,实现数据源的动态切换。
如果整合使用了Mybatis-Plus,则还需要添加Mybatis-Plus的相关配置,否则会报错,如下所示
@Configuration
@MapperScan(basePackages = {"com.practice.mapper"}, sqlSessionTemplateRef = "sqlTemplateDefault")
public class MybatisConfig {
@Bean(name = "sqlFactoryDefault")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dynamicDataSource);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*/*.xml"));
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
mybatisConfiguration.setLogImpl(StdOutImpl.class);
factoryBean.setConfiguration(mybatisConfiguration);
return factoryBean.getObject();
}
@Bean(name = "sqlTemplateDefault")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlFactoryDefault") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
3、利用AOP动态切换
自定义数据源标识注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
String value() default "masterDataSource";
}
利用AOP实现动态切换数据源
@Component
@Aspect
@Slf4j
public class DynamicDataSourceAspect {
private static final String DEFAULT_DATASOURCE = "masterDataSource";
@Pointcut("@annotation(com.practice.annotation.DS)")
public void dsPointCut() {}
@Before("dsPointCut() && @annotation(ds)")
public void changeDataSource(JoinPoint joinPoint, DS ds) {
String dataSourceName = ds.value();
if (DynamicDataSourceContextHolder.containsDataSource(dataSourceName)) {
DynamicDataSourceContextHolder.setDataSource(dataSourceName);
log.info("切换到数据源:{}", dataSourceName);
} else {
log.error("数据源不存在:{}", dataSourceName);
}
}
@After("@annotation(ds)")
public void clearDataSource(JoinPoint joinPoint, DS ds) {
DynamicDataSourceContextHolder.clear();
}
}