Spring Boot 数据库连接池 —— HikariCP

本文介绍 Spring Boot 2 配置 HikariCP 数据库连接池的方法。


目录

  • HikariCP 简介
  • Spring Boot HikariCP
    • 配置项说明
  • 开发环境
  • 代码示例

HikariCP 简介

HikariCP 是一个非常轻量级(130 KB 左右)且效率很高的 JDBC 连接池框架。

与已有的其它 JDBC 连接池技术(c3p0、dbcp2、tomcat、druid)相比,HikariCP 的速度非常快,因为它采用了以下技术:

  • 字节码精简:优化代码,直至编译后的字节码最少;
  • 优化代理和拦截器:减少代码量,如:HikariCP 的 Statement proxy 只有 100 行,是 BoneCP 的十分之一;
  • 自定义集合类型 FastStatementList 代替 ArrayList:避免每次 get() 调用都进行范围检查,避免调用 remove() 时从头到尾扫描;
  • 自定义集合类型 ConcurrentBag:提高并发读写效率;
  • 其他的一些微观优化:尽管几乎无法衡量,但这些优化相结合可提高整体性能。

Spring Boot HikariCP

Spring Boot 在 1.x 版本中默认使用 Tomcat JDBC 连接池,但到了 2.x 版本后就将 HikariCP 作为默认的数据库连接池(spring-boot-starter-jdbc 中包含了 HikariCP 连接池)。

Spring Boot 按照如下规则选用数据库连接池实现:

  1. 考虑到性能和并发,Spring Boot 优先使用 HikariCP;
  2. 其次选择 Tomcat 数据连接池;
  3. 如果 HikariCP 和 Tomcat 数据连接池都不可用,而 Commons DBCP2 可用,那么选择 Commons DBCP2。

可以通过设置 spring.datasource.type 属性绕过以上规则。

配置项说明


配置项:allow-pool-suspension
类型:java.lang.Boolean
构造器默认值:false
默认配置 validate 后的值:false
说明:控制池是否可以通过 JMX 暂停和恢复。


配置项:auto-commit
类型:java.lang.Boolean
构造器默认值:true
默认配置 validate 后的值:true
说明:自动提交从池中返回的连接。


配置项:catalog
类型:java.lang.String
构造器默认值:数据库驱动默认
默认配置 validate 后的值:null
说明:为支持 catalog 概念的数据库设置默认 catalog。


配置项:connection-init-sql
类型:java.lang.String
构造器默认值:null
默认配置 validate 后的值:null
说明: 该属性设置一个SQL语句,在将每个新连接创建后,将其添加到池中之前执行该语句。


配置项:connection-test-query
类型:java.lang.String
构造器默认值:
默认配置 validate 后的值:
说明:如果驱动程序支持 JDBC4,强烈建议不要设置此属性。


配置项:connection-timeout
类型:java.lang.Long
构造器默认值:SECONDS.toMillis(30) = 30000
默认配置 validate 后的值:30000
说明:等待来自池的连接的最大毫秒数,如果小于 250 毫秒,则被重置回 30 秒。


配置项:data-source-class-name
类型:java.lang.String
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:data-source-j-n-d-i
类型:java.util.Properties
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:driver-class-name
类型:java.lang.String
构造器默认值:null
默认配置 validate 后的值:null
说明:HikariCP 将尝试通过仅基于 jdbcUrlDriverManager 解析驱动程序,但对于一些较旧的驱动程序, 还必须指定 driverClassName


配置项:health-check-properties.
类型:java.util.Properties
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:health-check-registry
类型:java.lang.Object
构造器默认值:
默认配置 validate 后的值:
说明:该属性允许指定池使用的 Codahale / Dropwizard HealthCheckRegistry 的实例来报告当前健康信息。


配置项:idle-timeout
类型:java.lang.Long
构造器默认值:MINUTES.toMillis(10) = 600000
默认配置 validate 后的值:600000
说明:连接允许在池中闲置的最长时间。如果 idleTimeout + 1 秒 > maxLifetimemaxLifetime > 0,则会被重置为 0(代表永远不会退出);如果 idleTimeout != 0 且小于 10 秒,则会被重置为 10 秒。


配置项:initialization-fail-timeout
类型:java.lang.Long
构造器默认值:1
默认配置 validate 后的值:1
说明:如果池无法成功初始化连接,则此属性控制池是否将 fail fast。


配置项:isolate-internal-queries
类型:java.lang.Boolean
构造器默认值:false
默认配置 validate 后的值:false
说明:是否在其自己的事务中隔离内部池查询,例如连接活动测试。


配置项:jdbc-url
类型:java.lang.String
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:leak-detection-threshold
类型:java.lang.Long
构造器默认值:0
默认配置 validate 后的值:0
说明:记录消息之前连接可能离开池的时间量,表示可能的连接泄漏。如果大于 0 且不是单元测试,则进一步判断:(leakDetectionThreshold < SECONDS.toMillis(2) or (leakDetectionThreshold > maxLifetime && maxLifetime > 0),会被重置为 0,即如果要生效则必须大于 0,而且不能小于 2 秒,而且当 maxLifetime > 0 时不能大于 maxLifetime.


配置项:login-timeout
类型:java.lang.Integer
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:max-lifetime
类型:java.lang.Long
构造器默认值:MINUTES.toMillis(30) = 1800000
默认配置 validate 后的值:1800000
说明:池中连接最长生命周期,如果不等于 0 且小于 30 秒则会被重置回 30 分钟。


配置项:maximum-pool-size
类型:java.lang.Integer
构造器默认值:-1
默认配置 validate 后的值:10
说明:池中最大连接数,包括闲置和使用中的连接。如果小于 1,则会被重置。当 minIdle <= 0 被重置为 DEFAULT_POOL_SIZE 则为 10,如果 minIdle > 0 则重置为 minIdle 的值。
metric-registry | java.lang.Object | | | 该属性允许指定一个 Codahale / Dropwizard MetricRegistry 的实例,供池使用以记录各种指标。


配置项:metrics-tracker-factory
类型:com.zaxxer.hikari.metrics.MetricsTrackerFactory
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:minimum-idle
类型:java.lang.Integer
构造器默认值:-1
默认配置 validate 后的值:10
说明:池中维护的最小空闲连接数,如果小于 0 或大于 maximum-pool-size 则被重置为 maximum-pool-size


配置项:password
类型:java.lang.String
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:pool-name
类型:java.lang.String
构造器默认值:null
默认配置 validate 后的值:HikariPool-1
说明:连接池的用户定义名称,主要出现在日志记录和 JMX 管理控制台中以识别池和池配置。


配置项:read-only
类型:java.lang.Boolean
构造器默认值:false
默认配置 validate 后的值:false
说明:从池中获取的连接是否默认处于只读模式。


配置项:register-mbeans
类型:java.lang.Boolean
构造器默认值:false
默认配置 validate 后的值:false
说明:是否注册JMX管理Bean(MBeans)。


配置项:scheduled-executor
类型:java.util.concurrent.ScheduledExecutorService
构造器默认值:null
默认配置 validate 后的值:null
说明:此属性允许您设置将用于各种内部计划任务的 java.util.concurrent.ScheduledExecutorService 实例。


配置项:schema
类型:java.lang.String
构造器默认值:数据库驱动默认
默认配置 validate 后的值:null
说明:该属性为支持模式概念的数据库设置默认模式。


配置项:transaction-isolation
类型:java.lang.String
构造器默认值:null
默认配置 validate 后的值:null
说明:控制从池返回的连接的默认事务隔离级别。


配置项:username
类型:java.lang.String
构造器默认值:
默认配置 validate 后的值:
说明:


配置项:validation-timeout
类型:java.lang.Long
构造器默认值:SECONDS.toMillis(5) = 5000
默认配置 validate 后的值:5000
说明:连接将被测试活动的最大时间量,如果小于 250 毫秒,则会被重置回 5 秒。


开发环境

  • JDK 8
  • MySQL 8.x

代码示例

  1. 创建存储用户信息的数据表。
CREATE TABLE `test`.`user` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `account` VARCHAR(30) NOT NULL,
  `name` VARCHAR(60) NOT NULL,
  `birth` DATE NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `id_UNIQUE` (`id` ASC),
  UNIQUE INDEX `account_UNIQUE` (`account` ASC));
  1. 创建 Spring Boot 工程,参考:IntelliJ IDEA 创建 Spring Boot 工程。

  2. 在生成的 pom 文件中添加以下依赖:

    • mysql-connector-java:MySQL 数据库驱动
    • spring-boot-starter-jdbc:Spring JDBC 支持


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.6.RELEASE
        
    
    tutorial.spring.boot
    spring-boot-hikari
    0.0.1-SNAPSHOT
    spring-boot-hikari
    Demo project for Spring Boot

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
        
            mysql
            mysql-connector-java
            8.0.19
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


  1. 修改 application.yml,添加数据源配置。
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      pool-name: hikari-jdbc-pool
      minimum-idle: 10
      maximum-pool-size: 20
      connection-timeout: 60000
      idle-timeout: 60000
      validation-timeout: 3000
      max-lifetime: 600000
  1. 新建 DataSourceConfig 配置类,在工程创建时实例化一个 Spring JdbcTemplate 实例(Spring JdbcTemplate 是 Spring 框架提供的对 JDBC 的简单封装,JdbcOperationsJdbcTemplate 实现的接口)。
package tutorial.spring.boot.hikari.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    public JdbcOperations jdbcOperations(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
  1. 编写领域模型类,通常类属性和表中字段一一对应。
package tutorial.spring.boot.hikari.domain;

import java.time.LocalDateTime;
import java.util.Objects;

public class User {

    private Long id;

    private String account;

    private String name;

    private LocalDateTime birth;

    public User() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDateTime getBirth() {
        return birth;
    }

    public void setBirth(LocalDateTime birth) {
        this.birth = birth;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        User user = (User) o;
        return Objects.equals(id, user.id) &&
                Objects.equals(account, user.account) &&
                Objects.equals(name, user.name) &&
                Objects.equals(birth, user.birth);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, account, name, birth);
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", account='" + account + '\'' +
                ", name='" + name + '\'' +
                ", birth=" + birth +
                '}';
    }
}
  1. 编写数据访问层(DAO)接口代码。
package tutorial.spring.boot.hikari.dao;

import tutorial.spring.boot.hikari.domain.User;

public interface UserDao {

    /**
     * 新增
     */
    int insert(User user);

    /**
     * 查询
     */
    User get(String account);

    /**
     * 更新
     */
    int update(User user);

    /**
     * 删除
     */
    int delete();
}
  1. 编写数据访问层(DAO)接口实现代码。
package tutorial.spring.boot.hikari.dao.impl;

import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.stereotype.Repository;
import tutorial.spring.boot.hikari.dao.UserDao;
import tutorial.spring.boot.hikari.domain.User;

import java.sql.ResultSet;
import java.sql.SQLException;

@Repository
public class UserDaoImpl implements UserDao {

    private final JdbcOperations jdbcOperations;

    public UserDaoImpl(JdbcOperations jdbcOperations) {
        this.jdbcOperations = jdbcOperations;
    }

    @Override
    public int insert(User user) {
        String sql = "INSERT INTO `user` (`account`, `name`, `birth`) VALUES (?,?,?)";
        return jdbcOperations.update(sql, user.getAccount(), user.getName(), user.getBirth());
    }

    @Override
    public User get(String account) {
        String sql = "SELECT `id`, `account`, `name`, `birth` FROM `user` WHERE `account`=?";
        return jdbcOperations.queryForObject(sql, this::mapResult, account);
    }

    private User mapResult(ResultSet rs, int row)
            throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setAccount(rs.getString("account"));
        user.setName(rs.getString("name"));
        user.setBirth(rs.getTimestamp("birth").toLocalDateTime());
        return user;
    }

    @Override
    public int update(User user) {
        String sql = "UPDATE `user` SET `name`=?, `birth`=? WHERE `account`=?";
        return jdbcOperations.update(sql, user.getName(), user.getBirth(), user.getAccount());
    }

    @Override
    public int delete() {
        String sql = "DELETE FROM `user`";
        return jdbcOperations.update(sql);
    }
}
  1. 编写单元测试。
package tutorial.spring.boot.hikari.dao;

import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import tutorial.spring.boot.hikari.domain.User;

import java.time.LocalDateTime;

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserDaoTest {

    private static User user;

    @Autowired
    private UserDao userDao;

    @BeforeAll
    public static void init() {
        user = new User();
        user.setAccount("admin1");
        user.setName("Administrator");
        LocalDateTime birth = LocalDateTime.of(1985, 1, 1, 0, 0, 0);
        user.setBirth(birth);
    }

    @Test
    @Order(1)
    public void testNotNull() {
        Assertions.assertNotNull(userDao);
    }

    @Test
    @Order(2)
    public void testInsert() {
        Assertions.assertEquals(1, userDao.insert(user));
    }

    @Test
    @Order(3)
    public void testGet() {
        User result = userDao.get(user.getAccount());
        System.out.println(result);
        Assertions.assertEquals(user.getAccount(), result.getAccount());
        Assertions.assertEquals(user.getName(), result.getName());
        Assertions.assertEquals(user.getBirth(), result.getBirth());
    }

    @Test
    @Order(4)
    public void testUpdate() {
        user.setName("Master");
        user.setBirth(LocalDateTime.of(1970, 1, 1, 0, 0, 0));
        Assertions.assertEquals(1, userDao.update(user));
    }

    @Test
    @Order(5)
    public void testDelete() {
        Assertions.assertTrue(userDao.delete() > 0);
    }
}

测试结果略。

你可能感兴趣的:(Spring Boot 数据库连接池 —— HikariCP)