SpringBoot+MybatisPlus+dynamic-datasources实现连接Postgresql和mysql多数据源:
SpringBoot+MybatisPlus+dynamic-datasources实现连接Postgresql和mysql多数据源-CSDN博客
上面实现通过注解和配置文件的方式去进行多数据源操作。
如果业务需求,比如查询第三方接口时提供的是sqlserver的视图连接方式时,需要在调用
接口时手动新增数据源-检验数据源是否可用-切换当前数据源-查询数据-清除当前数据源
实现以上流程,可以通过mybatisplus的dynamic-datasource来实现。
开源文档付费
基础必读(免费) · dynamic-datasource · 看云
但是可以通过引入依赖后查看其源码以及借助网上一些教程,可以找到源码中需要用到的几个类
注:
1、引入pom依赖
这里springboot的版本是2.6.13
dynamic-datasource的版本是3.2.1
??????? <dependency>
??????????? <groupId>com.baomidou</groupId>
??????????? <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
??????????? <version>3.2.1</version>
??????? </dependency>
引入msyql、sqlserver、postgresql所需的依赖
??????? <!--MySQL驱动-->
??????? <dependency>
??????????? <groupId>mysql</groupId>
??????????? <artifactId>mysql-connector-java</artifactId>
??????? </dependency>
??????? <dependency>
??????????? <groupId>org.postgresql</groupId>
??????????? <artifactId>postgresql</artifactId>
??????? </dependency>
??????? <!-- sqlserver-->
??????? <dependency>
??????????? <groupId>com.microsoft.sqlserver</groupId>
??????????? <artifactId>mssql-jdbc</artifactId>
??????????? <version>7.4.1.jre8</version>
??????? </dependency>
引入Mybatisplus所需依赖
??????? <dependency>
??????????? <groupId>com.baomidou</groupId>
??????????? <artifactId>mybatis-plus-boot-starter</artifactId>
??????????? <version>3.5.1</version>
??????? </dependency>
引入其他依赖
??????? <dependency>
??????????? <groupId>org.springframework.boot</groupId>
??????????? <artifactId>spring-boot-starter-web</artifactId>
??????? </dependency>
??????? <dependency>
??????????? <groupId>org.projectlombok</groupId>
??????????? <artifactId>lombok</artifactId>
??????????? <optional>true</optional>
??????? </dependency>???
??????? <dependency>
??????????? <groupId>org.springframework.boot</groupId>
??????????? <artifactId>spring-boot-starter-test</artifactId>
??????????? <scope>test</scope>
??????? </dependency>
2、这里使用默认的HikariCP数据库连接池,没使用Druid,流程一样
参考上面博客已经实现了连接Mysql和sqlserver多数据源
spring:
? datasource:
??? dynamic:
????? primary: master #设置默认的数据源或者数据源组,默认值即为master
????? strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
????? datasource:
??????? master:
?????????url:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
????????? username: root
????????? password: 123456
????????? driver-class-name: com.mysql.cj.jdbc.Driver
????????? dbcp2:
??????????? min-idle: 5??????????????????????????????? # 数据库连接池的最小维持连接数
??????????? initial-size: 5??????????????????????????? # 初始化连接数
??????????? max-total: 5?????????????????????????????? # 最大连接数
??????????? max-wait-millis: 150?????????????????????? # 等待连接获取的最大超时时间
??????? pg:
????????? url: jdbc:postgresql://127.0.0.1:5432/test
????????? username: postgres
????????? password: 123456
????????? driver-class-name: org.postgresql.Driver
????????? dbcp2:
??????????? min-idle: 5??????????????????????????????? # 数据库连接池的最小维持连接数
??????????? initial-size: 5??????????????????????????? # 初始化连接数
??????????? max-total: 5?????????????????????????????? # 最大连接数
??????????? max-wait-millis: 150?????????????????????? # 等待连接获取的最大超时时间
3、SpringBoot+dynamic-datasource使用DynamicRoutingDataSource获取当前所有数据源
代码实现:
@SpringBootTest
class DynamicDataSourceTest {
??? @Autowired
??? private DataSource dataSource;
??? /**
???? * 获取当前所有数据源
???? */
??? @Test
??? void getAllDataSource() {
??????? DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
??????? System.out.println(ds.getCurrentDataSources().keySet());
??? }
}
其中DataSource是javax.sql包下的。
javax.sql.DataSource 是 jdk 提供的接口,各个连接池厂商 和 Spring 都对 DataSource 进行了设计和实现。
javax.sql.DataSource 是连接到物理数据源的工厂接口。它是 java.sql.DriverManager 功能的替代者,
是获取数据库连接的首选方法。
DataSource 数据源在必要时可以修改它的属性。例如,如果将数据源移动到其他服务器,
则可以更改 DataSource 的属性,这样访问该数据源的代码不需要做任何更改就可以获取到达到目的。
单元测试运行结果:
4、SpringBoot+DynamicRoutingDataSources实现添加与删除数据源
这里通过代码将sqlserver的数据源添加到DynamicRoutingDataSource中
这就需要用到creator包下的各种创建器
这里是默认HikariCP的连接池,所以使用该创建器,如果是其它类型则使用对应的创建器。
数据源创建需要的参数这里新建一个DTO类用来赋值
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class DataSourceDTO {
??? private String dataSourceName;
??? private String driverClassName;
??? private String url;
??? private String username;
??? private String password;
}
编写单元测试
@SpringBootTest
class DynamicDataSourceTest {
??? @Autowired
??? private DataSource dataSource;
??? @Autowired(required = false)
??? private HikariDataSourceCreator hikariDataSourceCreator;
??? /**
???? * 获取当前所有数据源
???? */
??? @Test
??? void getAllDataSource() {
??????? DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
??????? System.out.println(ds.getCurrentDataSources().keySet());
??? }
??? /**
???? * 添加 与 删除 HikariCP数据源
???? */
??? @Test
??? void addHcpDataSource() {
??????? DataSourceDTO sqlserver = DataSourceDTO.builder()
??????????????? .dataSourceName("sqlserver")
??????????????? .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
??????????????? .url("jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test")
??????????????? .username("sa")
??????????????? .password("123456")
??????????????? .build();
??????? DataSourceProperty dataSourceProperty = new DataSourceProperty();
??????? BeanUtils.copyProperties(sqlserver,dataSourceProperty);
??????? DataSource sqlserverDataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
??????? DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
??????? ds.addDataSource(sqlserver.getDataSourceName(),sqlserverDataSource);
??????? System.out.println(ds.getCurrentDataSources().keySet());
??????? ds.removeDataSource(sqlserver.getDataSourceName());
??????? System.out.println(ds.getCurrentDataSources().keySet());
??? }
}
单元测试运行结果:
5、SpringBoot中+HikariCP实现检测数据源是否可用
预实现手动切换当前数据源,首先需要确保配置的数据源的相关参数可以正常连接可用
新建检测数据源的方法
??? /**
???? *? 检测数据源
???? * @param dataSourceDTO
???? * @return
???? */
??? private boolean checkDataBase(DataSourceDTO dataSourceDTO){
??????? Connection connection = null;
??????? try {
??????????? DataSource dataSource = creatHcpDataSource(dataSourceDTO);
??????????? connection = dataSource.getConnection();
??????????? Statement statement = connection.createStatement();
??????????? statement.execute("select * from s_user");
??????????? return true;
??????? } catch (Exception exception) {
??????????? return false;
??????? }finally {
??????????? try {
??????????????? if(null!=connection){
??????????????????? connection.close();
??????????????? }
??????????? } catch (SQLException throwables) {
??????????????? throwables.printStackTrace();
??????????? }
??????? }
??? }
这其中就是创建数据源并进行连接和执行sql,确保无异常发生则代表连接可用,这里执行的sql以及表名
均写死,其中s_user是sqlserver中新建的表。
用到创建数据源的方法creatHcpDataSource的实现
??? /**
???? * 创建Hcp datasource
???? * @param dataSourceDTO
???? * @return
???? */
??? private DataSource creatHcpDataSource(DataSourceDTO dataSourceDTO){
??????? HikariDataSource dataSource = new HikariDataSource();
??????? //控制客户端(即用户程序)等待池中连接的最长毫秒数。如果在没有连接可用的情况下超过此时间,则将抛出SQLException异常。最低可以接受的连接超时为250毫秒。默认值:3000(30秒)。这是一个很重要的问题排查指标
??????? dataSource.setConnectionTimeout(3000l);
??????? //HikariCP将尝试仅基于jdbcUrl通过DriverManager解析驱动程序,但对于某些较旧的驱动程序必须指定driverClassName。除非用户收到明显的错误消息,表明未找到驱动程序,否则可忽略此属性。默认值:无
??????? //dataSource.setDataSourceClassName(dataSourceDTO.getDriverClassName());
??????? dataSource.setJdbcUrl(dataSourceDTO.getUrl());
??????? dataSource.setUsername(dataSourceDTO.getUsername());
??????? dataSource.setPassword(dataSourceDTO.getPassword());
??????? //表示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。该属性的默认值:自动生成
??????? dataSource.setPoolName(dataSourceDTO.getDataSourceName());
??????? return dataSource;
??? }
相关参数和配置自行搜索和设置,这里重点关注
dataSource.setConnectionTimeout(3000l);
设置超时时间3秒。
控制客户端(即用户程序)等待池中连接的最长毫秒数。如果在没有连接可用的情况下超过此时间,则将抛出SQLException异常。
最低可以接受的连接超时为250毫秒。默认值:3000(30秒)。这是一个很重要的问题排查指标
dataSource.setPoolName
示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。该属性的默认值:自动生成
编写单元测试:
??? /**
???? * 检测数据源
???? */
??? @Test
??? void testDataSource(){
??????? DataSourceDTO sqlserver = DataSourceDTO.builder()
??????????????? .dataSourceName("sqlserver")
??????????????? .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
??????????????? .url("jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test")
??????????????? .username("sa")
??????????????? .password("123456")
??????????????? .build();
??????? boolean checkDataBase = checkDataBase(sqlserver);
??????? System.out.println(checkDataBase);
??????? DataSourceDTO sqlserverWrong = DataSourceDTO.builder()
??????????????? .dataSourceName("sqlserver")
??????????????? .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
??????????????? .url("jdbc:sqlserver://192.168.1.2:1433;DatabaseName=test")
??????????????? .username("sa")
??????????????? .password("123456")
??????????????? .build();
??????? boolean checkDataBaseWrong = checkDataBase(sqlserverWrong);
??????? System.out.println(checkDataBaseWrong);
??? }
第二个故意写了一个不存在的数据源的地址
运行单元测试:
6、SpringBoot+DynamicDataSourceContextHolder实现手动切换清除当前数据源
动态数据源切换的关键在于DynamicDataSourceContextHolder类,它提供了一种机制来存储当前使用的数据源。
它主要由两部分组成,一部分是线程本地的数据源容器,另一部分是管理动态数据源的数据源切换类。
查看其源码
主要用到的方法
push 设置当前线程数据源 如非必要不要手动调用,调用后确保最终清除
poll 清空当前线程数据源 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称
clear 强制清空本地线程? 防止内存泄漏,如手动调用了push可调用此方法确保清除
编写单元测试:
??? /**
???? * 手动切换数据源
???? */
??? @Test
??? void changeDataSource() {
??????? DataSourceDTO sqlserver = DataSourceDTO.builder()
??????????????? .dataSourceName("sqlserver")
??????????????? .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
??????????????? .url("jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test")
??????????????? .username("sa")
??????????????? .password("123456")
??????????????? .build();
??????? boolean checkDataBase = checkDataBase(sqlserver);
??????? if(checkDataBase){
??????????? DataSourceProperty dataSourceProperty = new DataSourceProperty();
??????????? BeanUtils.copyProperties(sqlserver,dataSourceProperty);
??????????? DataSource sqlserverDataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
??????????? DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
??????????? Set<String> dataSourceSet = ds.getCurrentDataSources().keySet();
??????????? if (!dataSourceSet.contains(sqlserver.getDataSourceName())) {
??????????????? ds.addDataSource(sqlserver.getDataSourceName(),sqlserverDataSource);
??????????? }
??????????? //push 设置当前线程数据源 如非必要不要手动调用,调用后确保最终清除
??????????? DynamicDataSourceContextHolder.push(sqlserver.getDataSourceName());
??????????? List<SUser> sUsers = sUserMapper.selectList(new LambdaQueryWrapper<>());
??????????? System.out.println(sUsers);
??????????? //DynamicDataSourceContextHolder.poll();
??????????? //poll 清空当前线程数据源 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称
??????????? //clear 强制清空本地线程? 防止内存泄漏,如手动调用了push可调用此方法确保清除
??????????? DynamicDataSourceContextHolder.clear();
??????????? ds.removeDataSource(sqlserver.getDataSourceName());
??????? }else {
??????????? System.out.println("数据源连接异常");
??????? }
??? }
这里一定要对数据源可用性进行校验,确保可用才进行数据源添加和切换当前数据源操作
其中SUser是Sqlserver中新建表对应的实体类
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "s_user")
public class SUser implements Serializable {
??? private static final long serialVersionUID = -5514139686858156155L;
??? private Integer id;
??? private String name;
??? private Integer age;
??? private String address;
}
调用查询的mapper接口实现
import com.badao.demo.entity.SUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface SUserMapper extends BaseMapper<SUser> {
}
单元测试运行结果
7、将以上手动切换数据源和执行业务逻辑的操作封装成工具类
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import java.util.function.Supplier;
/**
?* 数据源切换工具类
?*/
public class DynamicDSExecute {
??? public static <T> T execute(String dsName, Supplier<T> executor){
??????? try {
??????????? DynamicDataSourceContextHolder.push(dsName);
??????????? return executor.get();
??????? }finally {
??????????? DynamicDataSourceContextHolder.clear();
??????? }
??? }
}
这里用到了函数式接口,可参考如下
java8中常用函数式接口Supplier<T>、Consumer<T>、Function<T,R>、Predicate<T>使用示例:
java8中常用函数式接口Supplier<T>、Consumer<T>、Function<T,R>、Predicate<T>使用示例_java8 函数式编程supplier接口案例大全-CSDN博客
然后调用工具类时
??????????? Supplier<List<SUser>> supplier = () -> sUserMapper.selectList(new LambdaQueryWrapper<>());
??????????? List<SUser> sUsers = DynamicDSExecute.execute(sqlserver.getDataSourceName(),supplier);