当前位置:   article > 正文

Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程_springboot @configuration 可以获取数据库信息吗

springboot @configuration 可以获取数据库信息吗

 以前写过一篇教程,Springboot AOP方式切换多数据源(主从两库类似情况使用最佳):

https://blog.csdn.net/qq_35387940/article/details/100122788


网上大多流传的springboot系列的切换多数据源都是以上那种写死在配置文件里的方式,这样如果我需要切换的数据源有10个,那么这种方式会不会显得稍微有点繁琐了。

现在这篇介绍的流程是,我们把各个数据源的配置信息写在一张数据库表里,从数据库表去加载这些数据源信息,根据我们给每个数据源命名的id去切换数据源,操作对应的数据库。

OK,接下来我们开始(如果真的想弄懂,最好跟我一步步来)

首先准备多个数据库,test1 ,test2 ,test3  :

接下来,我们在test1中,创建表 databasesource ,相关的SQL语句:

  1. CREATE TABLE `databasesource` (
  2. `datasource_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据源的id',
  3. `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '连接信息',
  4. `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  5. `pass_word` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  6. `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '暂留字段',
  7. `databasetype` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据库类型'
  8. ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

然后往test1数据库中的表databasesource里填充test2 、test3 这两个数据库的相关配置信息(对应的数据库帐号密码改成自己的),相关的SQL语句:
ps:这里面的datasource_id的值,是我们后面手动切换数据源的是使用的数据源 id

  1. INSERT INTO `test1`.`databasesource`(`datasource_id`, `url`, `user_name`, `pass_word`, `code`, `databasetype`) VALUES ('dbtest2', 'jdbc:mysql://localhost:3306/test2?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull', 'root', 'root', NULL, 'mysql');
  2. INSERT INTO `test1`.`databasesource`(`datasource_id`, `url`, `user_name`, `pass_word`, `code`, `databasetype`) VALUES ('dbtest3', 'jdbc:mysql://localhost:3306/test3?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull', 'root', 'root', NULL, 'mysql');

 接下来,我们分别在test2数据库和test3数据库中都创建user表,相关的SQL语句:

  1. CREATE TABLE `user` (
  2. `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  3. `age` int(3) NULL DEFAULT NULL
  4. ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

然后往test2数据库的user表里面填充两条数据用于测试, 相关的SQL语句:

  1. INSERT INTO `test2`.`user`(`user_name`, `age`) VALUES ('数据库2-小明', 20);
  2. INSERT INTO `test2`.`user`(`user_name`, `age`) VALUES ('数据库2-小方', 17);

然后往test3数据库的user表里面填充两条数据用于测试, 相关的SQL语句:

  1. INSERT INTO `test3`.`user`(`user_name`, `age`) VALUES ('数据库3-啊强', 11);
  2. INSERT INTO `test3`.`user`(`user_name`, `age`) VALUES ('数据库3-啊木', 12);

OK,到这里我们的数据库模拟场景已经准备完毕了,接下来下面就是真正的核心环节了!

首先是pom.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.2.0.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.testDb</groupId>
  12. <artifactId>dbsource</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>dbsource</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-jdbc</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-web</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.mybatis.spring.boot</groupId>
  30. <artifactId>mybatis-spring-boot-starter</artifactId>
  31. <version>2.0.0</version>
  32. </dependency>
  33. <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
  34. <dependency>
  35. <groupId>org.projectlombok</groupId>
  36. <artifactId>lombok</artifactId>
  37. <version>1.16.10</version>
  38. </dependency>
  39. <dependency>
  40. <groupId>mysql</groupId>
  41. <artifactId>mysql-connector-java</artifactId>
  42. <scope>runtime</scope>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-test</artifactId>
  47. <scope>test</scope>
  48. </dependency>
  49. <!-- druid数据源驱动 1.1.10解决springboot从1.0——2.0版本问题-->
  50. <dependency>
  51. <groupId>com.alibaba</groupId>
  52. <artifactId>druid-spring-boot-starter</artifactId>
  53. <version>1.1.10</version>
  54. </dependency>
  55. </dependencies>
  56. <build>
  57. <plugins>
  58. <plugin>
  59. <groupId>org.springframework.boot</groupId>
  60. <artifactId>spring-boot-maven-plugin</artifactId>
  61. </plugin>
  62. </plugins>
  63. </build>
  64. </project>

紧接着,application.yml(这里面的数据库配置信息将作为默认数据库):

  1. spring:
  2. aop:
  3. proxy-target-class: true #true为使用CGLIB代理
  4. datasource:
  5. #nullCatalogMeansCurrent=true&
  6. url: jdbc:mysql://localhost:3306/test1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
  7. username: root
  8. password: root
  9. #新版mysql驱动配置方法
  10. driverClassName: com.mysql.cj.jdbc.Driver
  11. ###################以下为druid增加的配置###########################
  12. type: com.alibaba.druid.pool.DruidDataSource
  13. # 下面为连接池的补充设置,应用到上面所有数据源中
  14. # 初始化大小,最小,最大
  15. initialSize: 5
  16. minIdle: 5
  17. maxActive: 20
  18. # 配置获取连接等待超时的时间
  19. maxWait: 60000
  20. # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  21. timeBetweenEvictionRunsMillis: 60000
  22. # 配置一个连接在池中最小生存的时间,单位是毫秒
  23. minEvictableIdleTimeMillis: 300000
  24. validationQuery: SELECT 1 FROM DUAL
  25. testWhileIdle: true
  26. testOnBorrow: false
  27. testOnReturn: false
  28. # 打开PSCache,并且指定每个连接上PSCache的大小
  29. poolPreparedStatements: true
  30. maxPoolPreparedStatementPerConnectionSize: 20
  31. # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
  32. filters: stat,wall,log4j
  33. # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
  34. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  35. # 合并多个DruidDataSource的监控数据
  36. useGlobalDataSourceStat: true
  37. ###############以上为配置druid添加的配置########################################
  38. mybatis:
  39. type-aliases-package: com.testdb.dbsource.pojo #扫描包路径
  40. configuration:
  41. map-underscore-to-camel-case: true #打开驼峰命名
  42. config-location: classpath:mybatis/mybatis-config.xml
  43. server:
  44. port: 8097

 先创建DataSource.java实体类,数据源信息装配的时候用:

  1. import lombok.Data;
  2. import lombok.ToString;
  3. /**
  4. * @Author : JCccc
  5. * @CreateTime : 2019/10/22
  6. * @Description :
  7. **/
  8. @Data
  9. @ToString
  10. public class DataSource {
  11. String datasourceId;
  12. String url;
  13. String userName;
  14. String passWord;
  15. String code;
  16. String databasetype;
  17. }

接下来,创建DruidDBConfig.java:

这里主要是配置默认的数据源,配置Druid数据库连接池,配置sql工厂加载mybatis的文件,扫描实体类等

  1. import com.alibaba.druid.pool.DruidDataSource;
  2. import com.alibaba.druid.support.http.StatViewServlet;
  3. import com.alibaba.druid.support.http.WebStatFilter;
  4. import org.apache.ibatis.session.SqlSessionFactory;
  5. import org.mybatis.spring.SqlSessionFactoryBean;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.beans.factory.annotation.Qualifier;
  9. import org.springframework.beans.factory.annotation.Value;
  10. import org.springframework.boot.context.properties.ConfigurationProperties;
  11. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  12. import org.springframework.boot.web.servlet.ServletRegistrationBean;
  13. import org.springframework.context.annotation.Bean;
  14. import org.springframework.context.annotation.Configuration;
  15. import org.springframework.context.annotation.Primary;
  16. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  17. import org.springframework.core.io.support.ResourcePatternResolver;
  18. import org.springframework.transaction.annotation.EnableTransactionManagement;
  19. import javax.sql.DataSource;
  20. import java.sql.SQLException;
  21. import java.util.HashMap;
  22. import java.util.Map;
  23. /**
  24. * @Author : JCccc
  25. * @CreateTime : 2019/10/22
  26. * @Description :
  27. **/
  28. @Configuration
  29. @EnableTransactionManagement
  30. public class DruidDBConfig {
  31. private final Logger log = LoggerFactory.getLogger(getClass());
  32. // adi数据库连接信息
  33. @Value("${spring.datasource.url}")
  34. private String dbUrl;
  35. @Value("${spring.datasource.username}")
  36. private String username;
  37. @Value("${spring.datasource.password}")
  38. private String password;
  39. @Value("${spring.datasource.driverClassName}")
  40. private String driverClassName;
  41. // 连接池连接信息
  42. @Value("${spring.datasource.initialSize}")
  43. private int initialSize;
  44. @Value("${spring.datasource.minIdle}")
  45. private int minIdle;
  46. @Value("${spring.datasource.maxActive}")
  47. private int maxActive;
  48. @Value("${spring.datasource.maxWait}")
  49. private int maxWait;
  50. @Bean // 声明其为Bean实例
  51. @Primary // 在同样的DataSource中,首先使用被标注的DataSource
  52. @Qualifier("mainDataSource")
  53. public DataSource dataSource() throws SQLException {
  54. DruidDataSource datasource = new DruidDataSource();
  55. // 基础连接信息
  56. datasource.setUrl(this.dbUrl);
  57. datasource.setUsername(username);
  58. datasource.setPassword(password);
  59. datasource.setDriverClassName(driverClassName);
  60. // 连接池连接信息
  61. datasource.setInitialSize(initialSize);
  62. datasource.setMinIdle(minIdle);
  63. datasource.setMaxActive(maxActive);
  64. datasource.setMaxWait(maxWait);
  65. datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
  66. datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
  67. // datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
  68. datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
  69. datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
  70. datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
  71. String validationQuery = "select 1 from dual";
  72. datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
  73. datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
  74. datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  75. datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
  76. datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
  77. datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
  78. datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
  79. datasource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
  80. return datasource;
  81. }
  82. /**
  83. * 注册一个StatViewServlet druid监控页面配置1-帐号密码配置
  84. *
  85. * @return servlet registration bean
  86. */
  87. @Bean
  88. public ServletRegistrationBean druidStatViewServlet() {
  89. ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
  90. new StatViewServlet(), "/druid/*");
  91. servletRegistrationBean.addInitParameter("loginUsername", "admin");
  92. servletRegistrationBean.addInitParameter("loginPassword", "123456");
  93. servletRegistrationBean.addInitParameter("resetEnable", "false");
  94. return servletRegistrationBean;
  95. }
  96. /**
  97. * 注册一个:filterRegistrationBean druid监控页面配置2-允许页面正常浏览
  98. *
  99. * @return filter registration bean
  100. */
  101. @Bean
  102. public FilterRegistrationBean druidStatFilter() {
  103. FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(
  104. new WebStatFilter());
  105. // 添加过滤规则.
  106. filterRegistrationBean.addUrlPatterns("/*");
  107. // 添加不需要忽略的格式信息.
  108. filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
  109. return filterRegistrationBean;
  110. }
  111. @Bean(name = "dynamicDataSource")
  112. @Qualifier("dynamicDataSource")
  113. public DynamicDataSource dynamicDataSource() throws SQLException {
  114. DynamicDataSource dynamicDataSource = new DynamicDataSource();
  115. dynamicDataSource.setDebug(false);
  116. //配置缺省的数据源
  117. // 默认数据源配置 DefaultTargetDataSource
  118. dynamicDataSource.setDefaultTargetDataSource(dataSource());
  119. Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
  120. //额外数据源配置 TargetDataSources
  121. targetDataSources.put("mainDataSource", dataSource());
  122. dynamicDataSource.setTargetDataSources(targetDataSources);
  123. return dynamicDataSource;
  124. }
  125. @Bean
  126. public SqlSessionFactory sqlSessionFactory() throws Exception {
  127. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  128. sqlSessionFactoryBean.setDataSource(dynamicDataSource());
  129. //解决手动创建数据源后字段到bean属性名驼峰命名转换失效的问题
  130. sqlSessionFactoryBean.setConfiguration(configuration());
  131. // 设置mybatis的主配置文件
  132. ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  133. // Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");
  134. // sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);
  135. // 设置别名包
  136. // sqlSessionFactoryBean.setTypeAliasesPackage("com.testdb.dbsource.pojo");
  137. //手动配置mybatis的mapper.xml资源路径,如果单纯使用注解方式,不需要配置该行
  138. // sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mybatis/mapper/*.xml"));
  139. return sqlSessionFactoryBean.getObject();
  140. }
  141. /**
  142. * 读取驼峰命名设置
  143. *
  144. * @return
  145. */
  146. @Bean
  147. @ConfigurationProperties(prefix = "mybatis.configuration")
  148. public org.apache.ibatis.session.Configuration configuration() {
  149. return new org.apache.ibatis.session.Configuration();
  150. }
  151. }

 然后是用于手动切换数据源的 DBContextHolder.java:

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. /**
  4. * @Author : JCccc
  5. * @CreateTime : 2019/10/22
  6. * @Description :
  7. **/
  8. public class DBContextHolder {
  9. private final static Logger log = LoggerFactory.getLogger(DBContextHolder.class);
  10. // 对当前线程的操作-线程安全的
  11. private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
  12. // 调用此方法,切换数据源
  13. public static void setDataSource(String dataSource) {
  14. contextHolder.set(dataSource);
  15. log.info("已切换到数据源:{}",dataSource);
  16. }
  17. // 获取数据源
  18. public static String getDataSource() {
  19. return contextHolder.get();
  20. }
  21. // 删除数据源
  22. public static void clearDataSource() {
  23. contextHolder.remove();
  24. log.info("已切换到主数据源");
  25. }
  26. }

然后是核心,手动加载默认数据源、创建数据源连接、检查数据源连接、删除数据源连接等 ,DynamicDataSource.java:

  1. import com.alibaba.druid.pool.DruidDataSource;
  2. import com.alibaba.druid.stat.DruidDataSourceStatManager;
  3. import com.testdb.dbsource.pojo.DataSource;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  7. import org.springframework.util.StringUtils;
  8. import java.sql.Connection;
  9. import java.sql.DriverManager;
  10. import java.util.Map;
  11. import java.util.Set;
  12. public class DynamicDataSource extends AbstractRoutingDataSource {
  13. private boolean debug = true;
  14. private final Logger log = LoggerFactory.getLogger(getClass());
  15. private Map<Object, Object> dynamicTargetDataSources;
  16. private Object dynamicDefaultTargetDataSource;
  17. @Override
  18. protected Object determineCurrentLookupKey() {
  19. String datasource = DBContextHolder.getDataSource();
  20. if (!StringUtils.isEmpty(datasource)) {
  21. Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
  22. if (dynamicTargetDataSources2.containsKey(datasource)) {
  23. log.info("---当前数据源:" + datasource + "---");
  24. } else {
  25. log.info("不存在的数据源:");
  26. return null;
  27. // throw new ADIException("不存在的数据源:"+datasource,500);
  28. }
  29. } else {
  30. log.info("---当前数据源:默认数据源---");
  31. }
  32. return datasource;
  33. }
  34. @Override
  35. public void setTargetDataSources(Map<Object, Object> targetDataSources) {
  36. super.setTargetDataSources(targetDataSources);
  37. this.dynamicTargetDataSources = targetDataSources;
  38. }
  39. // 创建数据源
  40. public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
  41. try {
  42. try { // 排除连接不上的错误
  43. Class.forName(driveClass);
  44. DriverManager.getConnection(url, username, password);// 相当于连接数据库
  45. } catch (Exception e) {
  46. return false;
  47. }
  48. @SuppressWarnings("resource")
  49. // HikariDataSource druidDataSource = new HikariDataSource();
  50. DruidDataSource druidDataSource = new DruidDataSource();
  51. druidDataSource.setName(key);
  52. druidDataSource.setDriverClassName(driveClass);
  53. druidDataSource.setUrl(url);
  54. druidDataSource.setUsername(username);
  55. druidDataSource.setPassword(password);
  56. druidDataSource.setInitialSize(1); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
  57. druidDataSource.setMaxActive(20); //最大连接池数量
  58. druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象
  59. druidDataSource.setMinIdle(5); //最小连接池数量
  60. String validationQuery = "select 1 from dual";
  61. // if("mysql".equalsIgnoreCase(databasetype)) {
  62. // driveClass = DBUtil.mysqldriver;
  63. // validationQuery = "select 1";
  64. // } else if("oracle".equalsIgnoreCase(databasetype)){
  65. // driveClass = DBUtil.oracledriver;
  66. // druidDataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
  67. // druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(50);
  68. // int sqlQueryTimeout = ADIPropUtil.sqlQueryTimeOut();
  69. // druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout="+sqlQueryTimeout);//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
  70. // } else if("sqlserver2000".equalsIgnoreCase(databasetype)){
  71. // driveClass = DBUtil.sql2000driver;
  72. // validationQuery = "select 1";
  73. // } else if("sqlserver".equalsIgnoreCase(databasetype)){
  74. // driveClass = DBUtil.sql2005driver;
  75. // validationQuery = "select 1";
  76. // }
  77. druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
  78. druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
  79. druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
  80. druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
  81. druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  82. druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
  83. druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
  84. druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
  85. druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
  86. druidDataSource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
  87. druidDataSource.init();
  88. this.dynamicTargetDataSources.put(key, druidDataSource);
  89. setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSources
  90. super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
  91. log.info(key+"数据源初始化成功");
  92. //log.info(key+"数据源的概况:"+druidDataSource.dump());
  93. return true;
  94. } catch (Exception e) {
  95. log.error(e + "");
  96. return false;
  97. }
  98. }
  99. // 删除数据源
  100. public boolean delDatasources(String datasourceid) {
  101. Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
  102. if (dynamicTargetDataSources2.containsKey(datasourceid)) {
  103. Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
  104. for (DruidDataSource l : druidDataSourceInstances) {
  105. if (datasourceid.equals(l.getName())) {
  106. dynamicTargetDataSources2.remove(datasourceid);
  107. DruidDataSourceStatManager.removeDataSource(l);
  108. setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
  109. super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
  110. return true;
  111. }
  112. }
  113. return false;
  114. } else {
  115. return false;
  116. }
  117. }
  118. // 测试数据源连接是否有效
  119. public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
  120. try {
  121. Class.forName(driveClass);
  122. DriverManager.getConnection(url, username, password);
  123. return true;
  124. } catch (Exception e) {
  125. return false;
  126. }
  127. }
  128. @Override
  129. public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
  130. super.setDefaultTargetDataSource(defaultTargetDataSource);
  131. this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
  132. }
  133. /**
  134. * @param debug
  135. * the debug to set
  136. */
  137. public void setDebug(boolean debug) {
  138. this.debug = debug;
  139. }
  140. /**
  141. * @return the debug
  142. */
  143. public boolean isDebug() {
  144. return debug;
  145. }
  146. /**
  147. * @return the dynamicTargetDataSources
  148. */
  149. public Map<Object, Object> getDynamicTargetDataSources() {
  150. return dynamicTargetDataSources;
  151. }
  152. /**
  153. * @param dynamicTargetDataSources
  154. * the dynamicTargetDataSources to set
  155. */
  156. public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) {
  157. this.dynamicTargetDataSources = dynamicTargetDataSources;
  158. }
  159. /**
  160. * @return the dynamicDefaultTargetDataSource
  161. */
  162. public Object getDynamicDefaultTargetDataSource() {
  163. return dynamicDefaultTargetDataSource;
  164. }
  165. /**
  166. * @param dynamicDefaultTargetDataSource
  167. * the dynamicDefaultTargetDataSource to set
  168. */
  169. public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {
  170. this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;
  171. }
  172. public void createDataSourceWithCheck(DataSource dataSource) throws Exception {
  173. String datasourceId = dataSource.getDatasourceId();
  174. log.info("正在检查数据源:"+datasourceId);
  175. Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
  176. if (dynamicTargetDataSources2.containsKey(datasourceId)) {
  177. log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常...");
  178. //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
  179. DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
  180. boolean rightFlag = true;
  181. Connection connection = null;
  182. try {
  183. log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount());
  184. long activeCount = druidDataSource.getActiveCount();
  185. log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount);
  186. if(activeCount > 0) {
  187. log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace());
  188. }
  189. log.info("准备获取数据库连接...");
  190. connection = druidDataSource.getConnection();
  191. log.info("数据源"+datasourceId+"正常");
  192. } catch (Exception e) {
  193. log.error(e.getMessage(),e); //把异常信息打印到日志文件
  194. rightFlag = false;
  195. log.info("缓存数据源"+datasourceId+"已失效,准备删除...");
  196. if(delDatasources(datasourceId)) {
  197. log.info("缓存数据源删除成功");
  198. } else {
  199. log.info("缓存数据源删除失败");
  200. }
  201. } finally {
  202. if(null != connection) {
  203. connection.close();
  204. }
  205. }
  206. if(rightFlag) {
  207. log.info("不需要重新创建数据源");
  208. return;
  209. } else {
  210. log.info("准备重新创建数据源...");
  211. createDataSource(dataSource);
  212. log.info("重新创建数据源完成");
  213. }
  214. } else {
  215. createDataSource(dataSource);
  216. }
  217. }
  218. private void createDataSource(DataSource dataSource) throws Exception {
  219. String datasourceId = dataSource.getDatasourceId();
  220. log.info("准备创建数据源"+datasourceId);
  221. String databasetype = dataSource.getDatabasetype();
  222. String username = dataSource.getUserName();
  223. String password = dataSource.getPassWord();
  224. String url = dataSource.getUrl();
  225. String driveClass = "com.mysql.cj.jdbc.Driver";
  226. // if("mysql".equalsIgnoreCase(databasetype)) {
  227. // driveClass = DBUtil.mysqldriver;
  228. // } else if("oracle".equalsIgnoreCase(databasetype)){
  229. // driveClass = DBUtil.oracledriver;
  230. // } else if("sqlserver2000".equalsIgnoreCase(databasetype)){
  231. // driveClass = DBUtil.sql2000driver;
  232. // } else if("sqlserver".equalsIgnoreCase(databasetype)){
  233. // driveClass = DBUtil.sql2005driver;
  234. // }
  235. if(testDatasource(datasourceId,driveClass,url,username,password)) {
  236. boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);
  237. if(!result) {
  238. log.error("数据源"+datasourceId+"配置正确,但是创建失败");
  239. // throw new ADIException("数据源"+datasourceId+"配置正确,但是创建失败",500);
  240. }
  241. } else {
  242. log.error("数据源配置有错误");
  243. // throw new ADIException("数据源配置有错误",500);
  244. }
  245. }
  246. }

ok,然后是我们切换数据源使用的方法, 我们这里采用mybatis注解的方式获取test1数据库里的databasesource 表信息,然后根据我们传入对应的数据源id进行数据源切换:

DataSourceMapper.java :

  1. import com.testdb.dbsource.pojo.DataSource;
  2. import org.apache.ibatis.annotations.Mapper;
  3. import org.apache.ibatis.annotations.Select;
  4. import java.util.List;
  5. /**
  6. * @Author : JCccc
  7. * @CreateTime : 2019/10/23
  8. * @Description :
  9. **/
  10. @Mapper
  11. public interface DataSourceMapper {
  12. @Select("SELECT * FROM databasesource")
  13. List<DataSource> get();
  14. }

DBChangeService.java:

  1. import com.testdb.dbsource.pojo.DataSource;
  2. import java.util.List;
  3. /**
  4. * @Author : JCccc
  5. * @CreateTime : 2019/10/22
  6. * @Description :
  7. **/
  8. public interface DBChangeService {
  9. List<DataSource> get();
  10. boolean changeDb(String datasourceId) throws Exception;
  11. }

DBChangeServiceImpl.java:

  1. import com.testdb.dbsource.dbconfig.DBContextHolder;
  2. import com.testdb.dbsource.dbconfig.DynamicDataSource;
  3. import com.testdb.dbsource.mapper.DataSourceMapper;
  4. import com.testdb.dbsource.pojo.DataSource;
  5. import com.testdb.dbsource.service.DBChangeService;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import java.util.List;
  9. /**
  10. * @Author : JCccc
  11. * @CreateTime : 2019/10/22
  12. * @Description :
  13. **/
  14. @Service
  15. public class DBChangeServiceImpl implements DBChangeService {
  16. @Autowired
  17. DataSourceMapper dataSourceMapper;
  18. @Autowired
  19. private DynamicDataSource dynamicDataSource;
  20. @Override
  21. public List<DataSource> get() {
  22. return dataSourceMapper.get();
  23. }
  24. @Override
  25. public boolean changeDb(String datasourceId) throws Exception {
  26. //默认切换到主数据源,进行整体资源的查找
  27. DBContextHolder.clearDataSource();
  28. List<DataSource> dataSourcesList = dataSourceMapper.get();
  29. for (DataSource dataSource : dataSourcesList) {
  30. if (dataSource.getDatasourceId().equals(datasourceId)) {
  31. System.out.println("需要使用的的数据源已经找到,datasourceId是:" + dataSource.getDatasourceId());
  32. //创建数据源连接&检查 若存在则不需重新创建
  33. dynamicDataSource.createDataSourceWithCheck(dataSource);
  34. //切换到该数据源
  35. DBContextHolder.setDataSource(dataSource.getDatasourceId());
  36. return true;
  37. }
  38. }
  39. return false;
  40. }
  41. }

注意认真看看上面的changeDb这个方法里面的代码,这就是后续手动切换调用的方法。

 

接下来,写相关操作user表的代码,因为user表分别在test2、test3数据库里,这样用于我们切换到test2或者test3数据库操作这些数据。

User.java:

  1. import lombok.Data;
  2. import lombok.ToString;
  3. /**
  4. * @Author : JCccc
  5. * @CreateTime : 2019/10/22
  6. * @Description :
  7. **/
  8. @Data
  9. @ToString
  10. public class User {
  11. String userName;
  12. String age;
  13. }

UserMappper.java  (上面简单介绍了下使用注解的方式获取表数据,可能有些人不习惯,那么这里也使用传统的mapper.xml方式编写mysql语句):

  1. import com.testdb.dbsource.pojo.User;
  2. import org.apache.ibatis.annotations.Mapper;
  3. import java.util.List;
  4. /**
  5. * @Author : JCccc
  6. * @CreateTime : 2019/10/23
  7. * @Description :
  8. **/
  9. @Mapper
  10. public interface UserMapper {
  11. List<User> queryUserInfo();
  12. }

userMapper.xml(注意namespace命名空间对应的路径以及user实体类对应的路径):

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.testdb.dbsource.mapper.UserMapper">
  6. <!--查询所有用户信息-->
  7. <select id="queryUserInfo" resultType="com.testdb.dbsource.pojo.User">
  8. select *
  9. from user
  10. </select>
  11. </mapper>

顺便一提,我的mapper.xml放在了下面的这个目录结果里,这里的目录结构路径非常关键,因为我们是手动切换数据源,采取了手动配置SqlSessionFactory,需要我们自己去配置mapper.xml路径的,一开始的DruidDBConfig里面有相关的代码,可以去回顾下:

ps:
DruidDBConfig.java

 

顺带,mybatis-config.xml里面,我做了简单的配置:

其实关于驼峰命名方式开启,我们在手动配置的时候也特意做了配置代码的。

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <settings>
  7. <setting name="useGeneratedKeys" value="true"/>
  8. <setting name="useColumnLabel" value="true"/>
  9. <setting name="mapUnderscoreToCamelCase" value="true"/>
  10. </settings>
  11. </configuration>

到这里,我们已经可以开始测试,

创建UserController.java,写个简单的测试接口:

  1. import com.testdb.dbsource.dbconfig.DBContextHolder;
  2. import com.testdb.dbsource.pojo.User;
  3. import com.testdb.dbsource.service.DBChangeService;
  4. import com.testdb.dbsource.service.UserService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import java.util.List;
  9. /**
  10. * @Author : JCccc
  11. * @CreateTime : 2019/10/23
  12. * @Description :
  13. **/
  14. @RestController
  15. public class UserController {
  16. @Autowired
  17. private DBChangeService dbChangeServiceImpl;
  18. @Autowired
  19. UserService userService;
  20. /**
  21. * 查询所有
  22. * @return
  23. */
  24. @GetMapping("/test")
  25. public String test() throws Exception {
  26. //切换到数据库dbtest2
  27. String datasourceId="dbtest2";
  28. dbChangeServiceImpl.changeDb(datasourceId);
  29. List<User> userList= userService.queryUserInfo();
  30. System.out.println(userList.toString());
  31. //再切换到数据库dbtest3
  32. dbChangeServiceImpl.changeDb("dbtest3");
  33. List<User> userList3= userService.queryUserInfo();
  34. System.out.println(userList3.toString());
  35. //切回主数据源
  36. DBContextHolder.clearDataSource();
  37. return "ok";
  38. }
  39. }

 主要看代码注释,调用整合出来的changeDb方法,通过传送数据源id (dbtest2)

切换到对应的数据库,然后操作对应数据库。

 

项目运行起来,调用接口 /test :

 

然后我们来看看控制台输出内容:

OK,非常简单顺利,教程就到此。 

 

补充该实战教学的事务相关介绍配置和介绍:

实现动态数据源事务,找到该篇项目实例中的DruidDBConfig.java ,

将 我们实现的动态数据源加载类DynamicDataSource 添加到数据事务管理器里:

  1. @Bean
  2. public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
  3. return new DataSourceTransactionManager(dynamicDataSource);
  4. }

 

事务测试  (单个数据源的事务)

 

接着,给我们的新增User方法里面,故意搞个异常出来,测试下回滚是否生效:

在不指定任何异常时,使用注解事务,默认只对RuntimeException异常生效,所以咱们简单点,在啥时候想回滚,咱们就手动丢个RuntimeException异常出来。

写个测试接口,给db2 插入一条user信息,看看事务回滚情况:
 

  1. /**
  2. * 添加
  3. * @return
  4. */
  5. @GetMapping("/addTest")
  6. public String addTest() throws Exception {
  7. //切换到数据库dbtest2
  8. String datasourceId="dbtest2";
  9. dbChangeServiceImpl.changeDb(datasourceId);
  10. User user2=new User();
  11. user2.setUserName("db2用户");
  12. user2.setAge("11");
  13. userService.insertUser(user2);
  14. //切回主数据源
  15. DBContextHolder.clearDataSource();
  16. return "ok";
  17. }

调用该接口,可以看到在未抛出异常时,可以看到插入根据影响行数,提示是成功了,但后面咱们抛异常触发了事务回滚:

看下数据库没出现新增的数据,事务回滚成功:

 

 

事务测试  (多个数据源的事务)

 

可以上图这个场景,就是测试切换不同数据源的时候,目前这两个数据源的插入方法都开启了事务,我们把test2的插入User方法加了故意抛出异常触发事务的代码,看看对于不同数据源事务触发情况: 

看看调用该接口,控制台的输出:

再看看数据库情况,

test2数据库,

 

test3数据库,

 

可以看到在多个数据源的时候,还是只有单独的数据自己的事务起作用了(怎么解决这种情况? 文章末尾有介绍)。

 

如果只是用于主从数据库两个数据源的业务场景,那么该篇非常适合使用,因为只需保证主的事务,从库会同步数据。
而且如果仅仅是为了满足主从/读写的场景,大可不必从数据库读取数据源,使用AOP方式读取配置文件的数据源即可满足业务场景,也就是参考我这篇:https://blog.csdn.net/qq_35387940/article/details/100122788 

 

 

那该篇的动态数据源实战适合哪些场景呢?


一.业务场景需要 很多个不同数据源,进行数据获取,不仅仅是两个,这样一来在配置文件配置是基本不可取的。

二.进行多从数据源数据获取,加上逻辑分析筛选后, 插入或更新 某个数据库,只需为该数据库事务进行负责
 

例如我的使用场景:

公司各个小项目很多a,b,c,d,e,都用着自己项目的数据库。

而新项目接口需求,收到第三方的调用后,需要到a,b,c,d,e系统的数据库里读取出不同的核心数据,然后进行业务逻辑分析,然后生成新的订单,插入到新项目的主数据库里,并返回结果给第三方,我只需要对一个数据进行事务管理。

 

那如果就是想多个数据源事务可以一块回滚呢? 难道就没解决方案了吗?

很可以,就是需要有这种精神,不管你目前的业务场景有没有触及。值得敬佩的你,请看:

并不然,使用JTA分布式事务即可解决。
请看我这篇:

Springboot 整合druid+mybatis+jta分布式事务+多数据源aop注解动态切换 (一篇到位)

https://blog.csdn.net/qq_35387940/article/details/103474353

 

 

 

PS: 这篇文章我很久前写的,日常搬砖也比较忙,但是从评论里包括我其他的动态切换数据源文章里面得知,大家使用mybatis-plus来开发还是比较多的。

不了解mybatis-plus的可以看看我这篇简单的入门使用(https://blog.csdn.net/qq_35387940/article/details/103381713)。

回归这个问题,那么在该篇文章里,如果使用 mybatis-plus 的话,为了保证使用,需要做什么调整呢?

 

1. 找到配置文件 

2.把本文原先的 SqlSessionFactoryBean 调整为使用  MybatisSqlSessionFactoryBean ,其实也就是说换一下sqlSessionFactory()这个方法的代码即可:

代码:
 

  1. @Bean
  2. public SqlSessionFactory sqlSessionFactory() throws Exception {
  3. MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
  4. sqlSessionFactoryBean.setDataSource(dynamicDataSource());
  5. sqlSessionFactoryBean.setMapperLocations(
  6. new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
  7. // 设置mybatis的主配置文件
  8. ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  9. Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");
  10. sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);
  11. return sqlSessionFactoryBean.getObject();
  12. }

 

ok,我们来简单写个接口测试一下,

这里新建一个Mapper继承mybatis-plus里面的BaseMapper,service层我就省略了。

然后写个测试接口,从主数据源分别切换到db2和db3 ,分别插入一下数据:

  1. import com.testdb.dbsource.dbconfig.DBContextHolder;
  2. import com.testdb.dbsource.pojo.User;
  3. import com.testdb.dbsource.service.DBChangeService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. /**
  8. * @Author : JCccc
  9. * @CreateTime : 2020/10/10
  10. * @Description :
  11. **/
  12. @RestController
  13. public class UserPlusController {
  14. @Autowired
  15. private DBChangeService dbChangeServiceImpl;
  16. @Autowired
  17. UserPlusMapper userMapper;
  18. @GetMapping("/testPlus")
  19. public void testPlus() throws Exception {
  20. User User2 = new User();
  21. User2.setUserName("TEST insert db 2");
  22. User2.setAge("2");
  23. //切换到数据库dbtest2
  24. String datasourceId2="dbtest2";
  25. dbChangeServiceImpl.changeDb(datasourceId2);
  26. int effectNum2 = userMapper.insert(User2);
  27. System.out.println("db2添加后的影响行数:"+effectNum2);
  28. //切换到数据库dbtest3
  29. String datasourceId3="dbtest3";
  30. dbChangeServiceImpl.changeDb(datasourceId3);
  31. User User3 = new User();
  32. User3.setUserName("TEST insert db 3");
  33. User3.setAge("3");
  34. int effectNum3 = userMapper.insert(User3);
  35. System.out.println("db3添加后的影响行数:"+effectNum3);
  36. //切回主数据源
  37. DBContextHolder.clearDataSource();
  38. }
  39. }

调用接口,控制台打印:

然后是数据库分别都有了数据:

 

ok,那这个对于mybatis-plus的使用补充就到这。 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/article/detail/46287
推荐阅读
相关标签
  

闽ICP备14008679号