【多数据源系列】在Sping Cloud(Spring Boot)中基于AbstractRoutingDataSource 实现多数据源动态切换

阅读: 评论:0

【多数据源系列】在Sping Cloud(Spring Boot)中基于AbstractRoutingDataSource 实现多数据源动态切换

【多数据源系列】在Sping Cloud(Spring Boot)中基于AbstractRoutingDataSource 实现多数据源动态切换

  本文将以代码示例介绍在Spring Cloud中基于AbstractRoutingDataSource实现多数据源动态切换。

  • 如文章中有明显错误或者用词不当的地方,欢迎大家在评论区批评指正,我看到后会及时修改。
  • 如想要和博主进行技术栈方面的讨论和交流可私信我。

目录

1. 前言

1.1. 背景

1.2. 原理

1.2.1 核心原理

1.2.2. 源码解析

1.2.3.  AbstractRoutingDataSource类结构

2. 开发环境搭建

2.1. 所用版本工具

2.2. pom依赖

2.2.1. 父模块依赖

 2.2.2 数据源切换模块

3. 核心代码编写

3.1. 编写JDBCUtil

 3.2. 编写DataSourceComponent

3.3. 编写DataSourceContext

3.4. 编写 MultiRouteDataSource

4. 参考链接 


1. 前言

1.1. 背景

        在近几年的业务需求中,我碰到了几个需要支持动态数据源切换的需求场景,如数据库读写优化,后台改为读写分离;需要在一个界面中同时支持读取不同数据库的数据(如Postgres和Oracle)。

        以在一个界面中同时支持读取不同数据库的数据这一需求为例,要实现这一功能可以用微服务走远程调用解决,但是一个界面通常属于一类业务,一般我是不会在往下拆分模块的(我个人习惯是一类业务对应一个微服务模块如用户模块,审批模块,鉴权模块),故考虑到了使用动态切换数据源来实现这个功能需求,网上找了很多解决方案最终选择了AbstractRoutingDataSource 。

1.2. 原理

1.2.1 核心原理

        AbstractRoutingDataSource是 Spring Framework 中提供的一个抽象类,用于支持动态切换数据源,它的原理是运行时动态地确定当前线程应该使用哪个数据源。其中几个核心原理如下:

1. 数据源映射

        AbstractRoutingDataSource内部维护了一个数据源的映射表。这个映射表将一个标识(通常是一个线程本地变量)映射到具体的数据源。

2. 决定数据源

        在每次数据库操作之前,AbstractRoutingDataSource会根据当前线程的标识去映射表中查找对应的数据源。这个标识通常存储在一个线程本地变量中,确保每个线程都可以拥有自己的数据源。

3. 线程本地变量

        Spring 通常使用ThreadLocal 存储当前线程的上下文信息。在多线程环境中,每个线程都可以拥有自己的线程本地变量,这确保了线程间的数据隔离。

4. 切换数据源

        在执行数据库操作之前,AbstractRoutingDataSource 会通过线程本地变量找到当前线程应该使用的数据源,并在运行时切换到该数据源。

1.2.2. 源码解析

AbstractRoutingDataSource类图如下图所示:

1.2.3.  AbstractRoutingDataSource类结构

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {private Map<Object, Object> targetDataSources;private Object defaultTargetDataSource;private boolean lenientFallback = true;private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();// 省略其他成员变量和方法protected abstract Object determineCurrentLookupKey();@Overridepublic Connection getConnection() throws SQLException {return determineTargetDataSource().getConnection();}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return determineTargetDataSource().getConnection(username, password);}// 省略其他方法
}

1.  determineCurrentLookupKey方法

  determineCurrentLookupKey是一个抽象方法,它由具体的子类实现。这个方法的目的是确定当前线程应该使用的数据源的标识。在实际应用中,这个方法通常通过访问线程本地变量或其他上下文信息来获取标识。

2. getConnection 方法

  getConnection方法是从 AbstractDataSource继承而来的,它在每次获取连接时调用 determineTargetDataSource 方法来确定当前应该使用的数据源,然后返回该数据源的连接。

3. determineTargetDataSource 方法

  determineTargetDataSource 方法根据 determineCurrentLookupKey 的返回值选择目标数据源。如果找不到对应的数据源,则使用默认的数据源。

protected DataSource determineTargetDataSource() {Null(this.targetDataSources, "TargetDataSources property must be set");Object lookupKey = determineCurrentLookupKey();DataSource dataSource = (lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.defaultTargetDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource;
}

2. 开发环境搭建

2.1. 所用版本工具

依赖版本
Spring Boot2.6.3
Spring Cloud Alibaba2021.0.1.0
Spring Cloud 2021.0.1
java1.8

2.2. pom依赖

pom依赖包含两个模块的依赖内容,即父模块和数据源切换模块。

2.2.1. 父模块依赖

  <properties><mavenpiler.source>8</mavenpiler.source><mavenpiler.target>8</mavenpiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>&porting.outputEncoding>UTF-8</porting.outputEncoding><java.version>1.8</java.version><spring-cloud.version>2021.0.1</spring-cloud.version><cloud-alibaba.version>2021.0.1.0</cloud-alibaba.version><spring-boot.version>2.6.3</spring-boot.version></properties><dependencyManagement><dependencies><!-- springCloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

 2.2.2 数据源切换模块

	<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency><dependency><groupId&acle</groupId><artifactId>ojdbc8</artifactId><version>12.2.0.1.0</version></dependency><!--热部署 ctrl+f9--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>

3. 核心代码编写

3.1. 编写JDBCUtil

@Data
@Component
@RefreshScope
public class JDBCUtil {@Value("${primary-datasource.url}")private String url;@Value("${primary-datasource.user}")private String user;@Value("${primary-datasource.password}")private String password;//1.加载驱动static {try {Class.forName("org.postgresql.Driver");} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//2.获取连接public  Connection getConnection() {Connection conn = null;try {conn = Connection(url, user, password);} catch (SQLException e) {e.printStackTrace();}return conn;}//3.关闭连接public  void close(Connection conn, Statement st, ResultSet rs) {//关闭连接if (conn != null) {try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//关闭statementif (st != null) {try {st.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//关闭结果集if (rs != null) {try {rs.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void releaseResc(ResultSet resultSet, Statement statement, Connection connection) {try {if (resultSet != null && !resultSet.isClosed()) {resultSet.close();}} catch (SQLException e) {e.printStackTrace();}try {if (statement != null && !statement.isClosed()) {statement.close();}} catch (SQLException e) {e.printStackTrace();}try {if (connection != null && !connection.isClosed()) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}}

 3.2. 编写DataSourceComponent

@Configuration
public class DataSourceComponent{@Autowiredprivate JDBCUtil jdbcUtil;@Primary//表示优先被注入@Bean(name = "multiDataSource")public MultiRouteDataSource exampleRouteDataSource() {MultiRouteDataSource multiDataSource = new MultiRouteDataSource();ResultSet resultSet = null;Statement statement = null;Connection connection = null;try {//采用jdbc访问主数据库connection = Connection();statement = ateStatement();String sql = "select * from initialization_data_source";resultSet = uteQuery(sql);Map<Object, Object> targetDataSources = new HashMap<>();//遍历循环while (()) {//数据库urlString url = String("url");//用户名String userName = String("user_name");//密码String password = String("password");//数据源名称String connection_name = String("connection_name");//驅動String driverClassName= String("driver_class_name");//创建Hikari数据库连接池HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(url);dataSource.setUsername(userName);dataSource.setPassword(password);dataSource.setDriverClassName(driverClassName);//Hikari数据池的配置dataSource.addDataSourceProperty("initialSize",8);dataSource.addDataSourceProperty("minIdle",5);dataSource.addDataSourceProperty("maxActive",20);dataSource.addDataSourceProperty("maxWait",60000);dataSource.addDataSourceProperty("timeBetweenEvictionRunsMillis",60000);dataSource.addDataSourceProperty("minEvictableIdleTimeMillis",300000);//把datasource放入map 多数据源每个key对应一个数据源targetDataSources.put(connection_name,dataSource);//数据库留有一条主数据源if(connection_name.equals("master")){//把此主数据源设置为默认加载multiDataSource.setDefaultTargetDataSource(dataSource);}}// 设置多数据源. key value的形式multiDataSource.setTargetDataSources(targetDataSources);return multiDataSource;} catch (Exception e) {e.printStackTrace();} finally {//释放资源leaseResc(resultSet, statement, connection);}return null;}
}

上述代码的作用为在项目启动时读取 initialization_data_source指定初始数据源(connection_name为master)。

initialization_data_source我上传到我的资源里了,需要的同学可以自行去下载=1001.2014.3001.5503

3.3. 编写DataSourceContext

@Component
public class DataSourceContext {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSource(String value) {contextHolder.set(value);}public static String getDataSource() {();}public static void clearDataSource() {ve();}
}

定义ThreadLocal,通过setDataSource(value)函数指定数据源标识key,AbstractRoutingDataSource会根据当前线程的标识去映射表中查找对应的数据源,完成数据源切换。

3.4. 编写 MultiRouteDataSource

public class MultiRouteDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {//通过绑定线程的数据源上下文实现多数据源的动态切换DataSource();}}

        完成上述代码后仅需要将DataSourceContext注入到需要做代码切换的地方,即可通过setDataSource(String value)切换数据源(ps:只能在controller中切换),记得在末尾要执行clearDataSource(),否则会造成内存泄露。

4. 参考链接 

SpringBoot——动态数据源(多数据源自动切换)-CSDN博客

SpringBoot 动态配置数据源_为什么需要动态数据源-CSDN博客

本文发布于:2024-01-28 22:13:39,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170645122610654.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:系列   动态   Sping   Spring   Cloud
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23