java每日精进 3.06 【多数据源】

数据库连接池(Database Connection Pool)

基本信息

是一种用于管理数据库连接的技术。它通过预先创建一定数量的数据库连接,并将其缓存在池中,供多个客户端或应用程序使用,从而减少了每次请求时连接数据库的开销。

主要特点

  1. 连接复用:连接池维护一组数据库连接,当应用程序需要数据库连接时,连接池会提供一个空闲连接;当应用程序完成数据库操作后,连接会被返回连接池,以便下次使用。

  2. 性能优化:减少了频繁打开和关闭数据库连接的开销,提高了数据库操作的效率。尤其在高并发环境下,能够显著提升应用程序的性能。

  3. 配置灵活性:连接池一般允许配置最大连接数、最小连接数、最大等待时间等参数,根据应用需求进行调整。

连接池的工作流程

  1. 初始化:当连接池启动时,会创建一定数量的数据库连接,并将它们放入池中。这个数量通常是根据应用的需求进行设置的。

  2. 获取连接:当应用程序需要执行数据库操作时,它从连接池中获取一个可用连接。如果池中没有空闲连接,它可以根据配置等待或者创建新的连接(如果池没有达到最大连接数)。

  3. 使用连接:应用程序通过获取的连接执行数据库操作(例如,查询、插入、更新等)。

  4. 归还连接:操作完成后,应用程序将连接归还给连接池,连接池将该连接标记为可用状态,等待下次使用。

  5. 关闭连接:当连接池被销毁时,池中的所有连接将被关闭,释放数据库资源。

常见的连接池框架

  1. HikariCP:一个高性能的JDBC连接池,广泛应用于Java应用中。
  2. C3P0:另一个JDBC连接池,支持自动测试、自动回收等功能。
  3. DBCP(Apache Commons DBCP):由Apache Commons提供的连接池实现,功能较为基础。
  4. Tomcat JDBC Connection Pool:Tomcat服务器自带的连接池,优化过的DBCP版本。

优点

  • 提高性能:通过复用连接避免了每次都建立和关闭数据库连接的开销。
  • 降低数据库负载:通过控制连接数,避免了数据库过载。
  • 提升响应速度:能够快速获取数据库连接,缩短请求响应时间。

缺点

  • 资源限制:如果连接池的最大连接数设置不合理,可能导致资源浪费或连接耗尽。
  • 复杂性:需要管理连接池的配置和监控连接的状态。

配置连接池的一些常见参数

  • 最小连接数(minIdle):连接池中至少保持的空闲连接数。
  • 最大连接数(maxTotal):连接池中允许的最大连接数。
  • 最大等待时间(maxWaitMillis):当连接池没有空闲连接时,应用程序等待连接的最长时间。
  • 连接最大空闲时间(maxIdleTime):连接在池中空闲的最长时间,超过这个时间连接会被关闭。

通过合理的配置和使用数据库连接池,可以显著提高数据库操作的效率和性能,尤其是在并发量较高的情况下。

Druid连接池(为监控而生的数据库连接池)

Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。

扫描二维码关注公众号,回复: 17593905 查看本文章

功能类别

功能

Druid

HikariCP

DBCP

Tomcat-jdbc

C3P0

性能

PSCache

LRU

SLB负载均衡支持

稳定性

ExceptionSorter

扩展

扩展

Filter

JdbcIntercepter

监控

监控方式

jmx/log/http

jmx/metrics

jmx

jmx

jmx

支持SQL级监控

Spring/Web关联监控

诊断支持

LogFilter

连接泄露诊断

logAbandoned

安全

SQL防注入

支持配置加密

从上表可以看出,Druid连接池在性能、监控、诊断、安全、扩展性这些方面远远超出竞品。

1. 连接池的性能消耗占比

  • 连接池本身的性能消耗在整个调用链路中通常占比不大。
    这是因为连接池的主要作用是管理和复用数据库连接,而不是直接处理业务逻辑。连接池的性能开销主要集中在连接的创建、销毁和管理上,但这些操作相比实际的数据库查询和业务处理来说,消耗相对较小。


2. 连接池的性能关键点

  • 连接是否以 LRU(Least Recently Used,最近最少使用)的方式重用
    LRU 是一种缓存淘汰算法,用于决定哪些连接应该被优先复用。通过 LRU 策略,连接池可以确保最常用的连接被保留,而不常用的连接被淘汰,从而提高连接的复用率,减少创建新连接的开销。

  • 是否支持 PSCache(PreparedStatement Cache)
    PreparedStatement 是预编译的 SQL 语句,可以重复使用,避免每次执行 SQL 时重新编译的开销。PSCache 的作用是缓存这些 PreparedStatement 对象,从而提升 SQL 执行的性能。如果连接池支持 PSCache,可以显著减少数据库的 CPU 和内存消耗。


3. DruidDataSource 的性能表现

  • DruidDataSource 是一个高性能的数据库连接池实现。

  • 在没有使用 Filter(过滤器)且没有打开 testOnBorrow(从连接池获取连接时是否检测连接的有效性)的情况下,DruidDataSource 的性能表现非常好(“裸测也是极好”)。
    这是因为:

    • 不开启 testOnBorrow 可以避免每次获取连接时的额外检测开销。

    • 不使用 Filter 可以减少额外的拦截和处理逻辑,从而提升性能。

连接池的性能优化重点在于:

  1. 连接的复用策略(如 LRU)。

  2. 是否支持 PreparedStatement 缓存(PSCache)。

  3. 减少不必要的检测和拦截逻辑(如 testOnBorrow 和 Filter)。

单数据源

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-19-datasource-pool-druid-single</artifactId>

    <dependencies>
        <!-- 保证 Spring JDBC 的依赖健全 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- 实现对 Druid 连接池的自动化配置 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency> <!-- 本示例,我们使用 MySQL -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!-- 实现对 Spring MVC 的自动化配置,因为我们需要看看 Druid 的监控功能 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

应用配置文件

spring:
  # datasource 数据源配置内容,对应 DataSourceProperties 配置属性类
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root # 数据库账号
    password: # 数据库密码
    type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
    # Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
    druid:
      min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
      max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
      filter:
        stat: # 配置 StatFilter ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilter
          log-slow-sql: true # 开启慢查询记录
          slow-sql-millis: 5000 # 慢 SQL 的标准,单位:毫秒
      stat-view-servlet: # 配置 StatViewServlet ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
        enabled: true # 是否开启 StatViewServlet
        login-username: admin # 账号
        login-password: 1234 # 密码

spring.datasource 配置项,设置 Spring 数据源的通用配置。其中,spring.datasource.type 配置项,需要主动设置使用 DruidDataSource 。

@SpringBootApplication
public class Application implements CommandLineRunner {

    private Logger logger = LoggerFactory.getLogger(Application.class);

    @Autowired
    private DataSource dataSource;

    public static void main(String[] args) {
        // 启动 Spring Boot 应用
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) {
        logger.info("[run][获得数据源:{}]", dataSource.getClass());
    }

}

多数据源

配置文件

spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
      # Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
      min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
      max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
    # 用户数据源配置
    users:
      url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
      # Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
      min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
      max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
    # Druid 自定已配置
    druid:
      # 过滤器配置
      filter:
        stat: # 配置 StatFilter ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilter
          log-slow-sql: true # 开启慢查询记录
          slow-sql-millis: 5000 # 慢 SQL 的标准,单位:毫秒
      # StatViewServlet 配置
      stat-view-servlet: # 配置 StatViewServlet ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
        enabled: true # 是否开启 StatViewServlet
        login-username: admin # 账号
        login-password: 123456 # 密码

数据源配置类

// DataSourceConfig.java

@Configuration
public class DataSourceConfig {

    /**
     * 创建 orders 数据源
     */
    @Primary
    @Bean(name = "ordersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.orders") // 读取 spring.datasource.orders 配置到 HikariDataSource 对象
    public DataSource ordersDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 创建 users 数据源
     */
    @Bean(name = "usersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.users")
    public DataSource usersDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

}

Application

HikariCP 

是一个高性能的 JDBC 连接池实现,被广泛认为是目前最快的连接池之一。以下是对 HikariCP 单数据源的详细介绍和配置说明:

单数据源

引入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-19-datasource-pool-hikaricp-single</artifactId>

    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency> <!-- 本示例,我们使用 MySQL -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
  • 无需主动引入 HikariCP 的依赖。因为在 Spring Boot 2.X 中,spring-boot-starter-jdbc 默认引入 com.zaxxer.HikariCP 依赖。

应用配置文件:

spring:
  # datasource 数据源配置内容,对应 DataSourceProperties 配置属性类
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root # 数据库账号
    password: # 数据库密码
    # HikariCP 自定义配置,对应 HikariConfig 配置属性类
    hikari:
      minimum-idle: 10 # 池中维护的最小空闲连接数,默认为 10 个。
      maximum-pool-size: 10 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。
  • 在 spring.datasource 配置项下,我们可以添加数据源的通用配置。
  • 在 spring.datasource.hikari 配置项下,我们可以添加 HikariCP 连接池。
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
        hikari:
          pool-name: HikariPool
          minimum-idle: 5          # 最小空闲连接数
          maximum-pool-size: 20    # 最大连接数
          idle-timeout: 30000      # 空闲连接超时时间(毫秒)
          max-lifetime: 1800000    # 连接的最大生命周期(毫秒)
          connection-timeout: 30000 # 连接超时时间(毫秒)
          connection-test-query: SELECT 1 # 连接测试查询
          auto-commit: true        # 是否自动提交事务
  • DataSourceConfiguration.Hikari会自动化配置 HikariCP 连接池。
// Application.java

@SpringBootApplication
public class Application implements CommandLineRunner {

    private Logger logger = LoggerFactory.getLogger(Application.class);

    @Autowired
    private DataSource dataSource;

    public static void main(String[] args) {
        // 启动 Spring Boot 应用
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) {
        try (Connection conn = dataSource.getConnection()) {
            // 这里,可以做点什么
            logger.info("[run][获得连接:{}]", conn);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

}

多数据源

配置文件

spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      # HikariCP 自定义配置,对应 HikariConfig 配置属性类
      hikari:
        minimum-idle: 20 # 池中维护的最小空闲连接数,默认为 10 个。
        maximum-pool-size: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。
    # 用户数据源配置
    users:
      url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      # HikariCP 自定义配置,对应 HikariConfig 配置属性类
      hikari:
        minimum-idle: 15 # 池中维护的最小空闲连接数,默认为 10 个。
        maximum-pool-size: 15 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。
@Bean(name = "ordersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.orders")
public DataSource ordersDataSource() {
   return DataSourceBuilder.create().build();
}

@Bean(name = "usersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.users")
public DataSource ordersDataSource() {
   return DataSourceBuilder.create().build();
}

以上配置

如果每个数据源如果有 HikariCP 的 "hikari" 自定义配置项时,它的自定义配置项无法设置到 HikariDataSource Bean 中。因为,"spring.datasource.orders.hikari" 是 "spring.datasource.orders" 的第二层属性。而 HikariDataSource的配置属性在第一层,这就导致无法正确的设置。

正确的示例

// DataSourceConfig.java

@Configuration
public class DataSourceConfig {

    /**
     * 创建 orders 数据源的配置对象
     */
    @Primary
    @Bean(name = "ordersDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.orders") // 读取 spring.datasource.orders 配置到 DataSourceProperties 对象
    public DataSourceProperties ordersDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 创建 orders 数据源
     */
    @Bean(name = "ordersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.orders.hikari") // 读取 spring.datasource.orders 配置到 HikariDataSource 对象
    public DataSource ordersDataSource() {
        // <1.1> 获得 DataSourceProperties 对象
        DataSourceProperties properties =  this.ordersDataSourceProperties();
        // <1.2> 创建 HikariDataSource 对象
        return createHikariDataSource(properties);
    }

    /**
     * 创建 users 数据源的配置对象
     */
    @Bean(name = "usersDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.users") // 读取 spring.datasource.users 配置到 DataSourceProperties 对象
    public DataSourceProperties usersDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 创建 users 数据源
     */
    @Bean(name = "usersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.users.hikari")
    public DataSource usersDataSource() {
        // 获得 DataSourceProperties 对象
        DataSourceProperties properties =  this.usersDataSourceProperties();
        // 创建 HikariDataSource 对象
        return createHikariDataSource(properties);
    }

    private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
        // 创建 HikariDataSource 对象
        HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        // 设置线程池名
        if (StringUtils.hasText(properties.getName())) {
            dataSource.setPoolName(properties.getName());
        }
        return dataSource;
    }

}
  • #ordersDataSourceProperties() 方法,创建 "orders" 数据源的 DataSourceProperties 配置对象。
    • @Primary注解,保证项目中有一个的 DataSourceProperties Bean 。
    • new DataSourceProperties() 代码段,会创建一个 DataSourceProperties 数据源的配置对象。
    • 搭配上 @Bean(name = "ordersDataSourceProperties") 注解,会创建一个名字为 "ordersDataSourceProperties" 的 DataSourceProperties Bean 。
    • @ConfigurationProperties(prefix = "spring.datasource.orders") 注解,会将 "spring.datasource.orders" 配置项,逐个属性赋值给 DataSourceProperties Bean 。
  • #ordersDataSource() 方法,创建 orders 数据源。
    • 调用 #ordersDataSourceProperties() 方法,获得 orders 数据源的 DataSourceProperties 。
    • 调用 #createHikariDataSource(DataSourceProperties properties) 方法,创建 HikariDataSource 对象。这样,"spring.datasource.orders" 配置项,逐个属性赋值给 HikariDataSource Bean 。
    • 搭配上 @Bean(name = "ordersDataSource") 注解,会创建一个名字为 "ordersDataSource" 的 HikariDataSource Bean 。
    • @ConfigurationProperties(prefix = "spring.datasource.orders.hikari") 注解,会将 HikariCP 的 "spring.datasource.orders.hikari" 自定义配置项,逐个属性赋值给 HikariDataSource Bean 。

创建 Application.java类,配置 @SpringBootApplication 注解即可。代码如下:

// Application.java

@SpringBootApplication
public class Application implements CommandLineRunner {

    private Logger logger = LoggerFactory.getLogger(Application.class);

    @Resource(name = "ordersDataSource")
    private DataSource ordersDataSource;

    @Resource(name = "usersDataSource")
    private DataSource usersDataSource;

    public static void main(String[] args) {
        // 启动 Spring Boot 应用
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) {
        // orders 数据源
        try (Connection conn = ordersDataSource.getConnection()) {
            // 这里,可以做点什么
            logger.info("[run][ordersDataSource 获得连接:{}]", conn);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        // users 数据源
        try (Connection conn = usersDataSource.getConnection()) {
            // 这里,可以做点什么
            logger.info("[run][usersDataSource 获得连接:{}]", conn);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_51721783/article/details/146061511
今日推荐