SpringBoot实现动态数据源切换

SpringBoot实现动态数据源切换
发表时间:2022-08-19
类型:

      一、springBoot是什么:springboot一种全新的编程规范,其设计目的是用来简化新Spring应用的初始搭建以及开发过程,SpringBoot也是一个服务于框架的框架,服务范围是简化配置文件。


      二、业务背景:我们公司为了简化模板部署操作。实现一个项目启动不同版本,连接不同数据库相同表获取数据。就需要动态切换到相关数据库,进行相关的操作。


      三、解决思路:我们是先建立一个保存所有连接信息的数据库,将所有的数据库连接信息放到一张表里。然后在项目启动时初始化,将所有连接放入连接池,根据对应得key值来连接对应数据库。我们在请求方面用拦截器拦截所有请求,在header中拿到key,切换到对应数据库进行操作。

四、实现方式
1、切换数据库

1、启动时初始化数据库连接池
application.yml配置文件中保存所有数据库的数据库

spring:
datasource:
moban:
jdbc-url: jdbc:mysql://127.0.0.1:3306/template_control_center?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: xxxxxx
password: x'x'x'x'x'x
driver-class-name: com.mysql.cj.jdbc.Driver
DataSourceConfiguration配置

@Configuration
public class DataSourceConfiguration {

Logger logger = LoggerFactory.getLogger(DataSourceConfiguration.class);


/*** Master data source. */
@Bean("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.moban")
DataSource masterDataSource() {
logger.info("create master datasource...");
return DataSourceBuilder.create().build();
}
}
这样在项目启动时就初始化时就连接好了初始数据库

INFO 6628 --- [ restartedMain] c.b.config.MyDataSourceConfiguratioin : create master datasource...
2、将所有数据库连接全部放入连接池
DataSourceConfiguration配置

@Configuration
public class DataSourceConfiguration {

Logger logger = LoggerFactory.getLogger(DataSourceConfiguration.class);
Map<Object, Object> map ;


/*** Master data source. */
@Bean("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.moban")
DataSource masterDataSource() {
logger.info("create master datasource...");
return DataSourceBuilder.create().build();
}

@Bean
@Primary
public DataSource getAll(@Autowired @Qualifier("masterDataSource")DataSource masterDataSource) {
map = new HashMap<>();
ResultSet resultSet= null;
Connection connection = null;
RoutingDataSource routing = new RoutingDataSource();
Statement stat =null;
try {
logger.info("create routing datasource...");
map.put("masterDataSource", masterDataSource);
routing = new RoutingDataSource();
routing.setTargetDataSources(map);
routing.setDefaultTargetDataSource(masterDataSource);

connection = masterDataSource.getConnection();
String sql = "select * from connections";
stat =connection.createStatement();
resultSet=stat.executeQuery(sql);
while(resultSet.next()){
Map<String, String> map2 = new HashMap();
map2.put(DruidDataSourceFactory.PROP_URL, resultSet.getString("url"));
logger.info("url="+resultSet.getString("url"));
//设置驱动Driver
map2.put(DruidDataSourceFactory.PROP_DRIVERCLASSNAME, "com.mysql.cj.jdbc.Driver");
//设置用户名
map2.put(DruidDataSourceFactory.PROP_USERNAME,"xxxx");
logger.info("username="+resultSet.getString("username"));
//设置密码
map2.put(DruidDataSourceFactory.PROP_PASSWORD,"xxxxxxxxxxx");
logger.info("password="+resultSet.getString("password"));

DataSource dataSource = DruidDataSourceFactory.createDataSource(map2);

map.put(resultSet.getString("sql_key"),dataSource);
logger.info("sql_key="+resultSet.getString("sql_key"));
}
routing.setTargetDataSources(map);
return routing;
}catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat!=null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return routing;
}

}

RoutingDataSource:设置数据连接

public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return RoutingDataSourceContext.getDataSourceRoutingKey();
}
}
RoutingDataSourceContext:

public class RoutingDataSourceContext {
// holds data source key in thread local:
static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();
public static String getDataSourceRoutingKey() {
String key = threadLocalDataSourceKey.get();
return key == null ? "masterDataSource" : key;
}
public RoutingDataSourceContext(String key) {
threadLocalDataSourceKey.set(key);
}
public void close() {
threadLocalDataSourceKey.remove();
}
}
这样,当想用那个数据库操作数据的时候,就可以设置哪个数据库连接。

2、Java 拦截器
1、拦截器是什么
既然要用拦截器,首先先得简单了解一下什么是拦截器:

  概念:java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。

  作用域:动态拦截Action调用的对象(也就是我们的controller层)

  我们日常开发中,经常会遇到这个场景:在访问系统功能前,需要用户登录,不登陆的话无法使用我们的系统,那么如果在每个方法前都加上登录代码...【emmm....我想应该不会有人这么干吧...】,常见的可以使用以下几种方式:

使用AOP切面功能来实现

使用Spring的拦截器相关接口来自定义拦截器

实现WebMvcConfigurer接口,重写addCorsMappings()方法和addInterceptors()方法【配置拦截器】
实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter,重写preHandle()方法【自定义拦截器】
  下面我们就一起来看下一下怎么实现吧~

2、代码实现
实现WebMvcConfigurer接口的方式
  WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。

  基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口。在Spring Boot 1.5版本都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等。SpringBoot 2.0 后,该类被标记为@Deprecated(弃用)。官方推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport。

  简单介绍一下WebMvcConfigurer中比较重要的几个方法:

addInterceptors:添加拦截器
addCorsMappings:跨域
addViewControllers:页面跳转(不用像现在我们要写一个Controller进行映射就可实现跳转)
addResourceHandlers:静态资源(自定义静态资源映射目录)
configureDefaultServletHandling:默认静态资源处理器
configureViewResolvers:视图解析器(配置请求视图映射,配置了以后我们返回一个页面路径的字符串时,解析器会帮我们拼装前缀和后缀等信息)
configureMessageConverters:信息转换器(比如我们入参的信息直接转换成json或者转换成对应的bean类就具体在这里配置)
拦截器配置源码:

@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")//指哪些接口URL需要增加跨域设置
.allowedOrigins("*")//指的是前端哪些域名被允许跨域
.allowCredentials(false)//需要带cookie等凭证时,设置为true,就会把cookie的相关信息带上
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")//指的是允许哪些方法
.maxAge(3600);//cookie的失效时间,单位为秒(s),若设置为-1,则关闭浏览器就失效
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册Interceptor拦截器(Interceptor这个类是我们自己写的拦截器类)
InterceptorRegistration registration = registry.addInterceptor(new Interceptor());
//addPathPatterns()方法添加需要拦截的路径
registration.addPathPatterns("/**"); //所有路径都被拦截
//excludePathPatterns()方法添加不拦截的路径
registration.excludePathPatterns( //添加不拦截路径
"/demo/loginPage", //登录页面的地址【不拦截】
"/**/*.html", //html静态资源
"/**/*.js", //js静态资源
"/**/*.css" //css静态资源
);
}

}
自定义拦截器源码:

public class Interceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("执行了Interceptor的preHandle方法");
try {
//统一拦截(获取)
Object key = request.getHeader("key");
String s = key.toString();
if (s != null) {
RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(s);
return true;
}
//这里设置拦截以后重定向的页面,一般设置为登陆页面地址
response.sendRedirect(request.getContextPath() + "");
} catch (IOException e) {
e.printStackTrace();
}
return true;//如果设置为false时,被请求时,拦截器执行到此处将不会继续操作
//如果设置为true时,请求将会继续执行后面的操作
}

}
这样我们拦截器从请求头中拿到key,这样就可以切换数据源到对应数据库操作数据


SpringBoot实现动态数据源切换

SpringBoot实现动态数据源切换

这就是moban和moban2数据库中的不同数据,只要请求key换成对应的就好。小程序开发、APP开发、平台建设、前沿技术培训与交流,合作请在公众号--“探秘本恒”-“联系我们”进行咨询。


留下您的联系方式,我们第一时间回复您