实际工作中,我们会遇到springboot项目初始化启动时候,不能指定具体连接哪个数据源的时候,不同的接口连接不同的数据源或者前端页面指定连接某个数据源等等情况,就会遇到动态数据源切换的问题。
一.通用的动态数据源切换场景
Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源.
1.继承AbstractRoutingDataSource,重写determineCurrentLookupKey方法
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = ThreadLocal.withInitial(() -> "defaultDataSource");
private static Map<Object,Object> dataSourceMap = new ConcurrentHashMap<>(10);
static {
dataSourceMap .put("defaultDataSource", SpringUtils.getBean("defaultDataSource"));
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSource.dataSourceKey.get();
}
public static void serDataSource(String dataSource){
DynamicDataSource.dataSourceKey.set(dataSource);
DynamicDataSource dynamicDataSource = (DynamicDataSource)SpringUtils.getBean("dataSource")
dynamicDataSource.afterPropertiesSet();
}
public static String getDataSource(){
return DynamicDataSource.dataSourceKey.get();
}
public static void clear(){
DynamicDataSource.dataSourceKey.remove();
}
2.把AbstractRoutingDataSource注入到容器中
@Configuration
public class DataSourceConfig{
@Bean
@ConfigurationProperties("spring.datasource.druid")
public DataSource defaultDataSource(){
return DruidDataSourceBuilder.create().builder();
}
@Bean
@Primary@DependsOn({"springUtils","defaultDataSource"})
public DynamicDataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource ();dynamicDataSource.setTargetDataSource(DynamicDataSource .dataSourceMap );
return dynamicDataSource ;
}}
3.实现SpringUtils工具类并注入到spring容器中
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> tClass){
return applicationContext.getBean(tClass);
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static HttpServletRequest getCurrentReq() {
ServletRequestAttributes requestAttrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttrs == null) {
return null;
}
return requestAttrs.getRequest();
}
public static String getMessage(String code, Object... args) {
LocaleResolver localeResolver = getBean(LocaleResolver.class);
Locale locale = localeResolver.resolveLocale(getCurrentReq());
return applicationContext.getMessage(code, args, locale);
}public satic String getProperty(String key){
return SpringUtils.applicationContext.getEnvironment.getProperty(key);
}
}
4.自定义注解和切面,实现方法级别是否切换数据源
@Document
@Retention(retentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OperDataSource {
}
@Component
@Aspect
public class DataSourceAspect {@Autowired
private DataSourceProperties basicProperties;
@Pointcut("@annotation(com.xxxxxx.OperDataSource )")
public void dataSourcePointCut(){}
@Before(value="dataSourcePointCut()")
public void beforeMerhod(JoinPoint jp){
DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setUrl(basicProperties.getUrl());
druidDataSource.setUsername(basicProperties.getUsername());
druidDataSource.setPassword(basicProperties.getPassword());
DynamicDataSource.dataSourceMap.put("dbKey",druidDataSource);
DataSourceContextHolder.setDataSource("dbKey");
}@After("dataSourcePointCut()")
public void afterMethod(JoinPoint jp){
DynamicDataSource.clear();
}
}
二. 页面切换数据源场景
1.排除掉Druid自动注入
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
2.手动实现DruidDataSource的bean
@Configuration
public class DataSourceConfig{
private DataSourceProperties basicProperties;
public DataSource dataSource() throws IOException, SQLException{
DruidDataSource druidDataSource = DruidDataSourceBuilder.create().buid();
druidDataSource.restart();
druidDataSource.setPassword(Base64.Encoder getEncoder(basicProperties.getPassword()));
}
}
3.自定义接口,实现重新设置数据源
DruidDataSource druidDataSource = (DruidDataSource)SpringUtils.getBean("dataSource");
druidDataSource.restart();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
......
如果需要改写yml文件内容
需要引入
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.26</version>
</dependency>