当前位置:   article > 正文

Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离_dynamicdatasourcecontextholder 连接超时时间

dynamicdatasourcecontextholder 连接超时时间

一、基础介绍

  多数据源字面意思,比如说二个数据库,甚至不同类型的数据库。在用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源。

二、项目目录截图

 

 三、多数据源SQL结构设计如下(简单的主从关系):

 

 PS:创建两个库用于搭建项目中主从使用不同的数据库,表可以随意定义。

 四、配置编码

1.数据源自定义注解,DataSource.java

  1. /**
  2. * 数据源自定义注解
  3. */
  4. @Target({ ElementType.METHOD, ElementType.TYPE })
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Documented
  7. @Inherited
  8. public @interface DataSource {
  9.     
  10. DataSourcesType name() default DataSourcesType.MASTER;
  11. }

 

2.数据源类型枚举类定义,DataSourcesType.java 

  1. /**
  2. * 数据源类型
  3. */
  4. public enum DataSourcesType {
  5. /**
  6. * 主库
  7. */
  8. MASTER,
  9. /**
  10. * 从库
  11. */
  12. SLAVE
  13. }

 3.多数据源application.yml配置文件配置

  1. # 数据源配置
  2. spring:
  3. datasource:
  4. type: com.alibaba.druid.pool.DruidDataSource
  5. driverClassName: com.mysql.cj.jdbc.Driver
  6. druid:
  7. master:
  8. url: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
  9. username: root
  10. password: 123456
  11. slave:
  12. enable: true
  13. url: jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
  14. username: root
  15. password: 123456
  16. # 初始连接数
  17. initialSize: 5
  18. # 最小连接池数量
  19. minIdle: 10
  20. # 最大连接池数量
  21. maxActive: 20
  22. # 配置获取连接等待超时的时间
  23. maxWait: 60000
  24. # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  25. timeBetweenEvictionRunsMillis: 60000
  26. # 配置一个连接在池中最小生存的时间,单位是毫秒
  27. minEvictableIdleTimeMillis: 300000
  28. # 配置一个连接在池中最大生存的时间,单位是毫秒
  29. maxEvictableIdleTimeMillis: 900000
  30. validationQuery: SELECT 1 FROM DUAL
  31. testWhileIdle: true
  32. testOnBorrow: false
  33. testOnReturn: false
  34. # 打开PSCache,并且指定每个连接上PSCache的大小
  35. poolPreparedStatements: true
  36. maxPoolPreparedStatementPerConnectionSize: 20
  37. # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,此处是filter修改的地方
  38. filters:
  39. commons-log.connection-logger-name: stat,wall,log4j
  40. # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
  41. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  42. # 合并多个DruidDataSource的监控数据
  43. useGlobalDataSourceStat: true
  44. # 配置 DruidStatFilter
  45. web-stat-filter:
  46. enabled: true
  47. url-pattern: /*
  48. exclusions: .js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
  49. stat-view-servlet:
  50. enabled: true
  51. url-pattern: /druid/*
  52. # IP 白名单,没有配置或者为空,则允许所有访问
  53. allow: 127.0.0.1
  54. # IP 黑名单,若白名单也存在,则优先使用
  55. deny: 192.168.31.253
  56. # 禁用 HTML 中 Reset All 按钮
  57. reset-enable: false
  58. # 登录用户名/密码
  59. login-username: root
  60. login-password: 123
  61. # 慢SQL记录
  62. filter:
  63. stat:
  64. enabled: true
  65. # 慢SQL记录
  66. log-slow-sql: true
  67. slow-sql-millis: 1000
  68. merge-sql: true
  69. wall:
  70. config:
  71. multi-statement-allow: true

4.数据源配置文件属性定义,DataSourceProperties.java

  1. /**
  2. * 数据源配置文件
  3. */
  4. @Setter
  5. @Configuration
  6. @ConfigurationProperties(prefix = "spring.datasource.druid")
  7. public class DataSourceProperties {
  8. private int initialSize;
  9. private int minIdle;
  10. private int maxActive;
  11. private int maxWait;
  12. private int timeBetweenEvictionRunsMillis;
  13. private int minEvictableIdleTimeMillis;
  14. private int maxEvictableIdleTimeMillis;
  15. private String validationQuery;
  16. private boolean testWhileIdle;
  17. private boolean testOnBorrow;
  18. private boolean testOnReturn;
  19. public DruidDataSource setDataSource(DruidDataSource datasource) {
  20. datasource.setInitialSize(initialSize);
  21. /** 配置初始化大小、最小、最大 */
  22. datasource.setInitialSize(initialSize);
  23. datasource.setMaxActive(maxActive);
  24. datasource.setMinIdle(minIdle);
  25. /** 配置获取连接等待超时的时间 */
  26. datasource.setMaxWait(maxWait);
  27. /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
  28. datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
  29. /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
  30. datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
  31. datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
  32. /**
  33. * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
  34. */
  35. datasource.setValidationQuery(validationQuery);
  36. /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
  37. datasource.setTestWhileIdle(testWhileIdle);
  38. /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
  39. datasource.setTestOnBorrow(testOnBorrow);
  40. /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
  41. datasource.setTestOnReturn(testOnReturn);
  42. return datasource;
  43. }

5.多数据源切换处理,DynamicDataSourceContextHolder.java

  1. /**
  2. * 数据源切换处理
  3. */
  4. public class DynamicDataSourceContextHolder {
  5. public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
  6. /**
  7. *此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
  8. */
  9. private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
  10. /**
  11. * 设置当前线程的数据源变量
  12. */
  13. public static void setDataSourceType(String dataSourceType) {
  14. log.info("已切换到{}数据源", dataSourceType);
  15. contextHolder.set(dataSourceType);
  16. }
  17. /**
  18. * 获取当前线程的数据源变量
  19. */
  20. public static String getDataSourceType() {
  21. return contextHolder.get();
  22. }
  23. /**
  24. * 删除与当前线程绑定的数据源变量
  25. */
  26. public static void removeDataSourceType() {
  27. contextHolder.remove();
  28. }
  29. }

 6.获取数据源(依赖于 spring) 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换,DynamicDataSource.java 

  1. /**
  2. * 获取数据源(依赖于 spring) 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换
  3. */
  4. public class DynamicDataSource extends AbstractRoutingDataSource {
  5. public static DynamicDataSource build() {
  6. return new DynamicDataSource();
  7. }
  8. /**
  9. * 获取与数据源相关的key
  10. * 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
  11. * 在通过determineTargetDataSource获取目标数据源时使用
  12. */
  13. @Override
  14. protected Object determineCurrentLookupKey() {
  15. return DynamicDataSourceContextHolder.getDataSourceType();
  16. }
  17. }

7.数据源核心配置类,DataSourceConfiguration.java 

  1. /**
  2. * 数据源配置类
  3. */
  4. @Configuration
  5. public class DataSourceConfiguration {
  6. /**
  7. * 主库
  8. */
  9. @Bean
  10. @ConfigurationProperties("spring.datasource.druid.master")
  11. public DataSource masterDataSource(DataSourceProperties dataSourceProperties) {
  12. return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());
  13. }
  14. /**
  15. * 从库
  16. */
  17. @Bean
  18. @ConditionalOnProperty( prefix = "spring.datasource.druid.slave", name = "enable", havingValue = "true")//是否开启数据源开关---若不开启 默认适用默认数据源
  19. @ConfigurationProperties("spring.datasource.druid.slave")
  20. public DataSource slaveDataSource(DataSourceProperties dataSourceProperties) {
  21. return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());
  22. }
  23. /**
  24. * 设置数据源
  25. */
  26. @Bean(name = "dynamicDataSource")
  27. @Primary
  28. public DynamicDataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
  29. Map<Object, Object> targetDataSources = new HashMap<>();
  30. DynamicDataSource dynamicDataSource = DynamicDataSource.build();
  31. targetDataSources.put(DataSourcesType.MASTER.name(), masterDataSource);
  32. targetDataSources.put(DataSourcesType.SLAVE.name(), slaveDataSource);
  33. //默认数据源配置 DefaultTargetDataSource
  34. dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
  35. //额外数据源配置 TargetDataSources
  36. dynamicDataSource.setTargetDataSources(targetDataSources);
  37. dynamicDataSource.afterPropertiesSet();
  38. return dynamicDataSource;
  39. }
  40. }

8.多数据源切面配置类,用于获取注解上的注解,进行动态切换数据源,DynamicDataSourceAspect.java

  1. @Aspect
  2. @Component
  3. @Order(-1) // 保证该AOP在@Transactional之前执行
  4. public class DynamicDataSourceAspect {
  5. protected Logger logger = LoggerFactory.getLogger(getClass());
  6. @Pointcut("@annotation(com.fuzongle.tankboot.common.annotation.DataSource)"
  7. + "|| @within(com.fuzongle.tankboot.common.annotation.DataSource)")
  8. public void dsPointCut() {
  9. }
  10. @Around("dsPointCut()")
  11. public Object around(ProceedingJoinPoint point) throws Throwable {
  12. Method targetMethod = this.getTargetMethod(point);
  13. DataSource dataSource = targetMethod.getAnnotation(DataSource.class);//获取要切换的数据源
  14. if (dataSource != null) {
  15. DynamicDataSourceContextHolder.setDataSourceType(dataSource.name().name());
  16. }
  17. try {
  18. return point.proceed();
  19. }
  20. finally {
  21. // 销毁数据源 在执行方法之后
  22. DynamicDataSourceContextHolder.removeDataSourceType();
  23. }
  24. }
  25. /**
  26. * 获取目标方法
  27. */
  28. private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
  29. Signature signature = pjp.getSignature();
  30. MethodSignature methodSignature = (MethodSignature) signature;
  31. Method agentMethod = methodSignature.getMethod();
  32. return pjp.getTarget().getClass().getMethod(agentMethod.getName(), agentMethod.getParameterTypes());
  33. }
  34. }
9.编写业务逻辑,切换从库查询数据。

 

 

 10.编写测试方法,调用查询业务,查看是否切换数据源是否生效。

PS:这种多数据源的动态切换确实可以解决数据的主从分库操作,但是却有一个致命的BUG,那就是事务不但失效而且无法实现

一致性,因为涉及到跨库,因此我们必须另想办法来实现事务的ACID原则

以上配置所有源码地址:

https://gitee.com/fuzongle/java-bucket

注意:

1.如果有任何不懂的地方可以关注公众号就可以加我微信,随时欢迎互相帮助。

2.技术交流群QQ:422167709。

3.如果希望学习更多,希望微信扫码,长按扫码,帮忙关注一下,举手之劳,当您无助的时候真的能帮你。非常感谢您关注公众号 "编程小乐"。

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/52419
推荐阅读
相关标签
  

闽ICP备14008679号