Dynamic Datasource多数据源切换

Dynamic Datasource多数据源切换

前言

距离Spring Boot3.0发布也即将过去一年时间了,但我相信有一部分开发者同我一样,在过去的很长一段时间对新版本都是持观望的态度,毕竟新版本所带来的不仅仅是便利,随之而来的也会有一些不可避免地小插曲。对一个项目而言,所注重的并不是你所使用的技术或版本多么超前,代码风格多么花里胡哨,而更侧重于整体的稳定性。

Spring Boot 3.0是Spring Boot框架的最新版本,它带来了许多新功能和改进。以下是Spring Boot 3.0发布的一些概述:

  1. 支持Java 17:Spring Boot 3.0完全支持Java 17,这意味着开发人员可以利用Java 17的新特性和改进来构建更强大的应用程序。

  2. 模块化支持:Spring Boot 3.0引入了模块化支持,使开发人员能够更好地组织和管理他们的应用程序代码。这使得应用程序更易于维护和扩展。

  3. 更强大的自动配置:Spring Boot 3.0进一步增强了自动配置功能,使开发人员能够更轻松地配置和部署应用程序。自动配置可以根据应用程序的依赖关系和环境自动配置各种组件和功能。

  4. 改进的性能和稳定性:Spring Boot 3.0通过优化内部实现和引入新的性能改进,提供了更好的性能和稳定性。这使得应用程序能够更高效地处理大量请求和负载。

  5. 新的监控和管理功能:Spring Boot 3.0引入了新的监控和管理功能,使开发人员能够更好地监视和管理他们的应用程序。这包括对应用程序的健康状况、性能指标和日志的监控和管理。

总之,Spring Boot 3.0是一个功能强大且易于使用的框架,它提供了许多新功能和改进,使开发人员能够更轻松地构建高性能和可扩展的应用程序。无论是新项目还是现有项目的升级,Spring Boot 3.0都是一个值得考虑的选择。

里程碑

SpringBoot是由Pivotal团队开发的一款用于简化Spring应用程序开发的框架。它于2013年首次发布,旨在解决传统Spring框架在配置繁琐、依赖管理复杂等方面的问题。以下是SpringBoot的发展历史:

  • 2013年:SpringBoot首次发布,引入了自动配置、嵌入式服务器等特性,大大简化了Spring应用程序的开发和部署。
  • 2014年:SpringBoot 1.0版本发布,引入了对Groovy语言的支持,提供了更加简洁的DSL(领域特定语言)来配置Spring应用程序。
  • 2015年:SpringBoot 1.2版本发布,引入了对Spring Cloud的支持,使得开发者可以更方便地构建微服务架构。
  • 2016年:SpringBoot 1.3版本发布,引入了对Spring Security的集成,提供了更加强大的安全性能。
  • 2017年:SpringBoot 2.0版本发布,引入了对Java 8的全面支持,同时对性能和稳定性进行了优化。
  • 2018年:SpringBoot 2.1版本发布,引入了对Kotlin语言的支持,提供了更加简洁和安全的编程体验。
  • 2019年:SpringBoot 2.2版本发布,引入了对Reactive编程模型的支持,使得开发者可以更好地构建响应式应用程序。
  • 2020年:SpringBoot 2.3版本发布,引入了对GraalVM的支持,提供了更高的性能和更小的内存占用。
Dynamic Datasource多数据源切换

SpringBoot的持续发展和不断更新使得开发者可以更加便捷地构建高性能、可扩展的应用程序。它已经成为Java开发领域中最受欢迎的框架之一。最终可以看到,官方对于2.0的支持也即将停止,它也终会淹没于迭代的浪潮之中 🐾

概述

官网:https://www.kancloud.cn/tracy5546/dynamic-datasource   [开源、文档收费]

dynamic-datasource是一个用于动态数据源切换的开源框架,它的发展史如下:

  1. 初期阶段:dynamic-datasource最初是在2017年由阿里巴巴的开发团队开发的。在这个阶段,dynamic-datasource主要解决了多数据源切换的问题,可以动态地在不同的数据源之间进行切换。

  2. 功能增强阶段:随着用户对dynamic-datasource的需求不断增加,开发团队陆续增加了一些功能来满足用户的需求。例如,支持读写分离、支持多租户、支持动态添加数据源等。

  3. 社区贡献阶段:dynamic-datasource逐渐受到了开发者社区的关注和认可,越来越多的开发者开始使用dynamic-datasource,并且积极参与到框架的开发和维护中。社区的贡献使得dynamic-datasource的功能得到了进一步的增强和完善。

  4. 生态建设阶段:随着dynamic-datasource的不断发展,一些相关的生态系统也开始建立起来。例如,一些与dynamic-datasource兼容的数据库连接池、ORM框架等工具的出现,进一步提升了dynamic-datasource的易用性和性能。

dynamic-datasource是一个基于Java的动态数据源框架,它可以帮助开发人员在多数据源的场景下灵活地切换和管理数据源。在传统的单数据源应用中,我们只需要配置一个数据源即可,但在一些复杂的应用中,可能需要同时连接多个不同的数据库。dynamic-datasource提供了一种简单而强大的方式来管理多个数据源,使得应用程序能够根据实际需求动态地选择和切换数据源。

使用dynamic-datasource,开发人员可以通过配置文件或编程方式定义多个数据源,并在运行时根据需要选择使用哪个数据源。这样,我们可以根据业务需求将不同的数据存储在不同的数据库中,或者将读操作和写操作分别分配到不同的数据库中,从而提高系统的性能和可扩展性。

dynamic-datasource还提供了一些高级功能,如动态添加和删除数据源、数据源的负载均衡、数据源的切换策略等。这些功能使得开发人员能够更加灵活地管理和使用多个数据源,从而更好地满足应用程序的需求。

总的来说,dynamic-datasource经过了初期阶段、功能增强阶段、社区贡献阶段和生态建设阶段的发展,逐渐成为了一个功能强大、易用性高的动态数据源切换框架。它在实际项目中的应用越来越广泛,为开发者提供了便利和灵活性。

快速开始

  1. 首先就是需要引入dynamic-datasource-spring-boot-starter依赖,请结合具体使用环境搭配。
  • SpringBoot 1.5.x、2.x.x版本:
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>
  • Spring Boot 3.x版本:
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
  <version>${version}</version>
</dependency>
  1. 然后就是修改application-datasource.yaml配置文件进行多数据源配置
spring:
  datasource:
    dynamic:
      primary: master #主库
      strict: false #严格模式-启动若匹配不到对应数据库则抛出异常
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/hub-boot?allowMultiQueries=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&nullDatabaseMeansCurrent=true
          username: root
          password: root
          init: # 初始化脚本、非必填
            schema: schema/tables.sql
        slave_1:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/nopua-server?allowMultiQueries=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&nullDatabaseMeansCurrent=true
          username: root
          password: root
  1. 最后,使用@DS注解进行切换数据源即可

@DS 可以注解在方法上或类上,同时存在就近原则,方法上注解优先于类上注解

@Service
@DS("slave")
public class UserServiceImpl implements UserService {
  @Autowired
  private JdbcTemplate jdbcTemplate;
  
  public List selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }
  
  @Override
  @DS("slave_1")
  public List selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
  
}

案例踩雷

多数据源(dynamic-datasource)+ Druid Monitor整合案例:

  • JDK 20
  • druid 1.2.20
  • dynamic-datasource 4.1.0

引入依赖

<!--...-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.0</version>
    <relativePath/>
</parent>

<properties>
    <java.version>20</java.version>
    <ibatis-plus.version>3.5.3.2</ibatis-plus.version>
    <dynamic.datasource.version>4.1.0</dynamic.datasource.version>
    <druid.version>1.2.20</druid.version>
</properties>

<dependencies>
    <!-- mybatis-plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${ibatis-plus.version}</version>
    </dependency>

    <!--mysql-->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>

    <!-- druid-monitor -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-3-starter</artifactId>
        <version>${druid.version}</version>
    </dependency>

    <!--dynamic datasource-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
        <version>${dynamic.datasource.version}</version>
    </dependency>
</dependencies>
<!--...-->

🐾 Mybatis-Plus整合配置,自行百度。

数据源配置

spring:
  # 排除默认的druid,但排出后不会再自动装配Servlet,无法使用Web页面
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    druid:
      max-active: 100
      initial-size: 3
      min-idle: 3
      max-wait: 30000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 30000
      pool-prepared-statements: true
      max-open-prepared-statements: 30
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      filter:
        wall:
          config:
            # 允许批处理
            multi-statement-allow: true
            # 不允许删表操作
            drop-table-allow: false
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: /*.js,/*.gif,/*.jpg,/*.bmp,/*.png,/*.css,/*.ico,/druid/*
        session-stat-enable: true
        profile-enable: true
      stat-view-servlet:
        enabled: true
        login-username: hub_admin
        login-password: hub_123456
        url-pattern: /druid/*
    dynamic:
      primary: master
      #严格模式-启动若匹配不到对应数据库则抛出异常
      strict: false
      datasource:
        master:
          # Druid
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/hub-boot?allowMultiQueries=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&nullDatabaseMeansCurrent=true
          username: root
          password: root
          init:
            schema: schema/tables.sql
          druid:
            initial-size: 3
            max-active: 8
            min-idle: 2
            max-wait: -1
            min-evictable-idle-time-millis: 30000
            max-evictable-idle-time-millis: 30000
            time-between-eviction-runs-millis: 0
            validation-query: select 1
            validation-query-timeout: -1
            test-on-borrow: false
            test-on-return: false
            test-while-idle: true
            pool-prepared-statements: true
            filters: stat,wall
            share-prepared-statements: true
        slave_1:
          # Druid
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/nopua-server?allowMultiQueries=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&nullDatabaseMeansCurrent=true
          username: root
          password: root
          druid:
            initial-size: 3
            max-active: 8
            min-idle: 2
            max-wait: -1
            min-evictable-idle-time-millis: 30000
            max-evictable-idle-time-millis: 30000
            time-between-eviction-runs-millis: 0
            validation-query: select 1
            validation-query-timeout: -1
            test-on-borrow: false
            test-on-return: false
            test-while-idle: true
            pool-prepared-statements: true
            filters: stat,wall
            share-prepared-statements: true

本来到这里就应该结束了,但是,经过踩雷之后,发现存在一个issues一直未得到解决,详见:

👉 https://github.com/alibaba/druid/issues/4875

直接启动程序,发现如果我们配置了:

spring:
  # 排除默认的druid,但排出后不会再自动装配Servlet,无法使用Web页面
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure

程序不会抛出异常,但无法使用Web应用页面。于是我就不排除DruidDataSourceAutoConfigure配置类,得到一个异常:

Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:

Consider the following:
 If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
 If you have database settings to be loaded from a particular profile you may need to activate it (the profiles camunda,datasource,ibatis,springdoc,web are currently active).

于是,我就去扒源码,发现源码中存在这样一个东西:

Dynamic Datasource多数据源切换

本来我以为是我版本用错了,就想着看看百度上有没有人遇到,然后就去看issues,发现也有人遇到过,但没有解决方案写在里面。但我又不可能退SpringBoot版本,因为我就是为了踩3.x的雷的目的来的。

于是一步步看源码,最后决定重写DruidDataSourceAutoConfigure配置类,排除默认的配置类。

额外配置

  • 重写DruidDataSourceAutoConfigure配置类
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidFilterConfiguration;
import com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidSpringAopConfiguration;
import com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidStatViewServletConfiguration;
import com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidWebStatFilterConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
@Primary
@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore(
{DataSourceAutoConfiguration.class})
@EnableConfigurationProperties(
{DruidStatProperties.classDataSourceProperties.class})
@Import(
{DruidSpringAopConfiguration.class,
        DruidStatViewServletConfiguration.class,
        DruidWebStatFilterConfiguration.class,
        DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure 
{
    public DruidDataSourceAutoConfigure() {
    }
}

和原来的区别在哪呢?

Dynamic Datasource多数据源切换

问题随之而来的就是装配DruidStatViewServletConfiguration的前提条件就是:

Dynamic Datasource多数据源切换

经过测试,Web监控页面也能访问,也不会抛出异常。

Dynamic Datasource多数据源切换

RAQ

⚠️ 注:以下内容引用作者回答!

  1. @DS 注解放在哪里合适?

DS作为切换数据源核心注解,我应该把他注解在哪里合适?这其实是初次接触多数据源的人常问的问题。 这其实没有一定的要求,只是有一些经验之谈。


📢 首先开发者要了解的基础知识是,DS注解是基于AOP的原理实现的,aop的常见失效场景应清楚。 比如内部调用失效,shiro代理失效。 具体见切换数据源失败。

✅ 作者通常建议DS放在serviceImpl的方法上,如事务注解一样。

  • 注解在Controller的方法上或类上

并不是不可以,作者并不建议的原因主要是controller主要作用是参数的检验等一些基础逻辑的处理,这部分操作常常并不涉及数据库。

  • 注解在service的实现类的方法或类上

这是作者建议的方式,service主要是对业务的处理, 在复杂的场景涉及连续切换不同的数据库。 如果你的方法有通用性,其他service也会调用你的方法。 这样别人就不用重复处理切换数据源。

  • 注解在mapper上

通常如果你某个Mapper对应的表只在确定的一个库,也是可以的。 但是建议只注解在Mapper的类上。

其他使用方式:

  • 继承抽象类上的DS

比如我有一个抽象Service,我想实现继承我这个抽象Service下的子Service的所有方法除非重新指定,都用我抽象Service上注解的数据源。 是否支持?
答:支持。

  • 继承接口上的DS

3.4.1开始支持,但是需要注意的是,一个类能实现多个接口,如果多个接口都有DS会如何?
⚠️ 不知道,别这么干。。。。

  1. DruidDataSourceWrapper必须要获取到环境变量否则抛异常?

详见 👉 https://github.com/alibaba/druid/issues/4875


原文始发于微信公众号(青衫大叔灬):Dynamic Datasource多数据源切换

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/179731.html

(1)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!