【SpringBoot教程】Spring Boot + MySQL + HikariCP 连接池整合教程

发布于:2025-02-10 ⋅ 阅读:(43) ⋅ 点赞:(0)
🙋大家好!我是毛毛张!
🌈个人首页: 神马都会亿点点的毛毛张

在前面一篇文章中毛毛张介绍了SpringBoot中数据源与数据库连接池相关概念,今天毛毛张要分享的是关于SpringBoot整合HicariCP连接池相关知识点以及底层源码分析

1 HicariCP连接池概述

  • 在上面介绍过HicariCPSpringBoot2.0之后默认的数据库连接池,特点就是:简单,高效,史称最快的数据库连接池,毛毛张将从以下几个方面展开对HicariCP连接池的介绍:
    • 首先简单介绍以下HicariCP连接池
    • 然后通过一个案例来帮助大家能够快速使用HicariCP连接池
    • 接着通过分析SpringBoot的底层代码来解释SringBoot默认的数据库连接池为HicariCP

1.1 HicariCP连接池简介

  • HikariCP 是一个高性能的JDBC数据库连接池,基于BoneCP做了多项优化,旨在提供更高的并发性能和更低的延迟。自SpringBoot 2.x版本后(自然也包括SpringBoot 3.x),HikariCP成为默认的数据库连接池,只需导入HikariCPJAR包并配置相关参数,即可无缝集成并优化数据库连接池管理。
  • HikariCP 的高性能优化主要体现在以下两个方面:
    • FastList 替代 ArrayList
      • 传统的数据库连接池大多使用ArrayList存储Statement,HikariCP 自定义了FastList来优化这一操作。FastList的优化主要体现在两个方面:
        • 取消 ArrayListget() 方法中的范围检查(range check)。由于数据库连接池管理的 List 中的索引合法性有保证,因此不需要每次访问都进行索引合法性检查。
        • 改变 ArrayListremove() 操作的遍历方式,采用从尾部开始遍历,而不是从头开始。由于连接池中的连接通常是逆序释放的(后获取的连接先释放),这样优化后,每次关闭连接时可以更高效地找到需要释放的资源,提升了效率。
    • ConcurrentBag 替代阻塞队列
      • 大多数传统数据库连接池使用两个阻塞队列(idlebusy)来管理空闲连接和忙碌连接,使用 Lock 机制来处理线程竞争,但这种方式在高并发场景下可能会导致性能瓶颈。HikariCP通过使用 ConcurrentBag 替代了传统的阻塞队列,极大地减少了锁的竞争,提高了并发性能。
      • ConcurrentBag的核心工作原理:
        • sharedList:存储所有数据库连接的共享队列,使用 CopyOnWriteArrayList 类型,支持并发操作。
        • threadList:线程本地存储,避免了线程竞争,每个线程会缓存自己获取的连接。
        • waiters:等待连接的线程数,使用 AtomicInteger 类型。
        • handoffQueue:分配数据库连接的核心队列,使用 SynchronousQueue 类型,负责将空闲连接分配给请求的线程。
      • 这种设计通过减少线程竞争和优化连接分配,提高了连接池的效率,特别适合高并发的环境。
  • 其他优化
    • 字节码精简:HikariCP 在字节码上进行了优化,编译后的字节码量极少,这样可以使得更多的代码被 CPU 缓存,从而提高程序的执行效率。减少字节码的大小是提高性能的基础,HikariCP 在这方面做得非常好。
    • 优化代理和拦截器:HikariCP 对代理和拦截器的实现进行了精简。例如,它的 Statement 代理类仅有 100 行代码,是 BoneCP 的十分之一。这减少了性能开销,确保数据库连接池在执行 SQL 时更加高效。
    • 自定义集合类型(FastStatementList):为了提高对 Statement 的管理效率,HikariCP 使用了自定义的集合类型 FastStatementList 来替代传统的 ArrayList。这样避免了每次获取元素时的范围检查,并且采用逆序遍历的方式来优化 remove() 操作,使得关闭连接时更加高效。
    • 自定义集合类型(ConcurrentBag):HikariCP 为了优化并发读写效率,采用了 ConcurrentBag 代替传统的阻塞队列。这不仅提高了数据库连接分配的效率,而且减少了锁的竞争,显著提升了高并发场景下的性能。
    • 针对 BoneCP 缺陷的优化:HikariCP 在设计时,针对 BoneCP 中的一些缺陷进行了优化,特别是在处理 CPU 时间片内的耗时方法调用时,进一步提高了性能。

接下来毛毛张将结合一个完整的项目案例来介绍SpringBoot整合MySQL和HikariCP 连接池的过程,并提供了完整的代码

2 快速入门案例

  • 案例内容:基于Spring Boot,使用MyBatis-Plus框架,结合HikariCP连接池,查询并展示数据库中的全部用户信息

2.1 后端代码

2.1.1 创建项目

2.1.2 导入依赖

  • 在 Spring Boot 项目中,默认使用 HikariCP 作为数据库连接池,因此在大多数情况下无需手动引入该依赖。若项目中使用了以下某些 starter 依赖,HikariCP 会自动作为连接池配置:
    • spring-boot-starter-jdbc
    • spring-boot-starter-data-jpa
    • mybatis-spring-boot-starter
    • mybatis-plus-boot-starter
  • 需要特别注意的是,mybatismybatis-plus 已经间接依赖了 spring-boot-starter-jdbc,因此这两个 starter 会自动引入 HikariCP,从而避免重复配置。
方式1:手动导入 HikariCP 依赖
  • 如果没有导入HikariCP,我们可以通过下面的方式手动导入HikariCP依赖
    <dependency>
       <groupId>com.zaxxer</groupId>
       <artifactId>HikariCP</artifactId>
       <version>6.2.1</version>
    </dependency>
    
方式 2:自动引入依赖 - 推荐
  • 在使用SpringBoot默认配置时,不需要手动添加 HikariCP 依赖,可以通过导入下面依赖的方式来自动引入HikariCP依赖:

    <!-- 引入 Spring Boot JDBC 模块 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    
    <!-- 引入 Spring Boot JPA 模块 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- 引入 MyBatis Plus 模块 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.2</version>
    </dependency>
    
本项目完整依赖
  • 需要注意的是,在使用 HikariCP 作为连接池的同时,还需要单独导入 MySQL 数据库驱动,通常通过引入 mysql-connector-java 依赖来实现
  • 毛毛张在这个任务中为了方便,使用了Mybatis-plus框架,整个项目的pom.xml文件为:
    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
    
        <groupId>com.zzx</groupId>
        <artifactId>springboot-HicariCP-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>springboot-HicariCP-demo</name>
        <description>springboot-HicariCP-demo</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-parent</artifactId>
            <version>2.7.6</version>
        </parent>
    
    
        <properties>
            <java.version>1.8</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <spring-boot.version>2.7.6</spring-boot.version>
        </properties>
    
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!-- MySQL 数据库驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope> <!-- 运行时依赖 -->
            </dependency>
    
            <!-- MybatisPlus 核心库 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.2</version>
            </dependency>
    
            <!-- 热部署 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                    <configuration>
                        <mainClass>com.zzx.SpringbootHicariCpDemoApplication</mainClass>
                        <skip>true</skip>
                    </configuration>
                    <executions>
                        <execution>
                            <id>repackage</id>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

2.1.3 编写配置文件

  • 下面是整个项目的配置文件application.yaml,关键部分已被注释出来了,更多详细的解释可以参见第三节的内容
    server:
      port: 8080
    spring:
      # HikariCP 连接池配置
      datasource:
        url: jdbc:mysql://localhost:3306/springboot?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: abc123
        # 指定数据源类型为 HikariDataSource
        type: com.zaxxer.hikari.HikariDataSource
        # Hikari 连接池的详细配置
        hikari:
          # 连接池名称
          pool-name: HikariCP
          # 最小空闲连接数
          minimum-idle: 5
          # 空闲连接超时时间(毫秒)
          idle-timeout: 600000
          # 连接池的最大大小
          maximum-pool-size: 10
          # 是否自动提交事务
          auto-commit: true
          # 连接的最大生命周期(毫秒)
          max-lifetime: 1800000
          # 连接超时时间(毫秒)
          connection-timeout: 30000
          # 测试连接的 SQL 语句
          connection-test-query: SELECT 1
    
    logging:
      level:
        org.springframework.jdbc.core.JdbcTemplate: DEBUG
    
    
    mybatis-plus:
      # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
      mapper-locations: classpath:mapper/*.xml
      configuration:
        auto-mapping-behavior: full
        # 开启驼峰映射
        map-underscore-to-camel-case: true
        # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
        call-setters-on-nulls: true
        # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

2.1.4 初始化数据库并创建对应实体类

  • 创建名为springboot的数据库,并创建user_info表:

    -- 创建数据库
    CREATE DATABASE IF NOT EXISTS springboot CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
    USE springboot;
    
    -- 删除已存在的 user_info 表(如果存在)
    DROP TABLE IF EXISTS `user_info`;
    
    -- 创建 user_info 表
    CREATE TABLE `user_info`  (
      `id` INT NOT NULL AUTO_INCREMENT,
      `user_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
      `pass_word` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
      `age` INT NULL DEFAULT NULL,
      `gender` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 3 
      DEFAULT CHARSET = utf8mb4 
      COLLATE = utf8mb4_0900_ai_ci 
      ROW_FORMAT = Dynamic;
    
    -- 插入示例数据
    INSERT INTO `user_info` (`id`, `user_name`, `pass_word`, `age`, `gender`) VALUES 
    (1, 'sam', 'password123', 32, 'M'),
    (2, 'hah', 'password456', 10, 'F');
    
    -- 确保外键检查被重新启用
    SET FOREIGN_KEY_CHECKS = 1;
    
    
  • 对应实体类User

    package com.zzx.entity;
    
    
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    
    @Data
    @TableName("user_info")
    public class User {
        private Integer id;        // 对应数据库中的 `u_id`
        private String userName;   // 对应数据库中的 `u_username`
        private String passWord;   // 对应数据库中的 `u_password`
        private Integer age;       // 对应数据库中的 `u_age`
        private String gender;     // 对应数据库中的 `u_gender`
    }
    
    

2.1.5 编写响应封装与前端展示VO

  • 定义统一的响应封装类 ResVo:
    package com.zzx.reponse;
    
    public class ResVo<T> {
        private Integer code;      // 状态码
        private String message;    // 消息内容
        private T content;         // 内容,可以是任何类型的数据
    
        // 构造方法
        public ResVo(Integer code, String message, T content) {
            this.code = code;
            this.message = message;
            this.content = content;
        }
    
        // 成功的返回,通常是常用的,内容可以为空
        public static <T> ResVo<T> success(T content) {
            return new ResVo<>(200, "成功", content);
        }
    
        // 失败的返回,通常返回错误信息
        public static <T> ResVo<T> error(Integer code, String message) {
            return new ResVo<>(code, message, null);
        }
    
        // Getters and Setters
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public T getContent() {
            return content;
        }
    
        public void setContent(T content) {
            this.content = content;
        }
    }
    
    
  • 前端展示对象UserInfoVo,在查询用户信息时通过是把查询到的结果封装在一个实体类中,但是返回给前端的信息不一定是用户的全部信息,例如用户的密码就不能直接返回给前端,或者不要返回,所以毛毛张设计了一个这个类:
    package com.zzx.model.vo;
    
    import lombok.Data;
    
    @Data
    public class UserInfoVo {
        //返回给前端展示的数据,密码不能展示,性别转化成数字
        private Integer id;
        private String username;
        private Integer age;
        private Integer gender;
    }
    
    
  • 为了节省传输的字符,毛毛张将用户的信息对应的内容转化成数字再返回给前端,因此设计了一个枚举类型UserSexEnum
    package com.zzx.enums;
    
    public enum UserSexEnum {
        M(1, "男"), // M对应男,值为 1
        F(0, "女"); // F对应女,值为 0
    
        private int code;         // 对应的数字值(1 或 0)
        private String description; // 性别描述(男 或 女)
    
        // 构造方法,用于设置枚举常量的描述和对应的代码
        UserSexEnum(int code, String description) {
            this.code = code;
            this.description = description;
        }
    
        // 获取性别描述
        public String getDescription() {
            return description;
        }
    
        // 获取对应的数字代码
        public int getCode() {
            return code;
        }
    
        // 根据传入的字符串 'M' 或 'F' 获取对应的性别枚举
        public static UserSexEnum fromString(String sexStr) {
            for (UserSexEnum sex : UserSexEnum.values()) {
                if (sex.name().equalsIgnoreCase(sexStr)) {
                    return sex;
                }
            }
            throw new IllegalArgumentException("无效的性别字符串: " + sexStr);
        }
    
        // 根据 'M' 或 'F' 获取对应的数字代码
        public static int getCodeByString(String sexStr) {
            UserSexEnum sex = fromString(sexStr);
            return sex.getCode();
        }
    }
    
  • 由于User类和UserInfoVo不是完全一一对应的,所以为了数据转换的方便,毛毛张再这里专门写了一个转换类UserConverter
    package com.zzx.converter;
    
    import com.zzx.entity.User;
    import com.zzx.enums.UserSexEnum;
    import com.zzx.model.vo.UserInfoVo;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class UserConverter{
        // 单个转换
        public static UserInfoVo toUserInfoDTO(User user) {
            UserInfoVo userInfoVo = new UserInfoVo();
            userInfoVo.setId(user.getId());
            userInfoVo.setUsername(user.getUserName());
            userInfoVo.setAge(user.getAge());
            userInfoVo.setGender(UserSexEnum.getCodeByString(user.getGender()));
            return userInfoVo;
        }
    
        // 批量转换
        public static List<UserInfoVo> toUserInfoDTOList(List<User> users) {
            // 使用 Java 8 的 stream API 进行批量转换
            return users.stream()
                    .map(UserConverter::toUserInfoDTO)  // 对每个 User 对象进行转换
                    .collect(Collectors.toList());     // 收集成 List<UserInfoDTO>
        }
    }
    

2.1.6 编写业务逻辑

  • controller层UserController
    package com.zzx.controller;
    
    import com.zzx.converter.UserConverter;
    import com.zzx.model.vo.UserInfoVo;
    import com.zzx.reponse.ResVo;
    import com.zzx.entity.User;
    import com.zzx.service.UserService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @RestController
    @RequestMapping("user")
    public class UserController {
        @Resource
        private UserService userService;
    
        @GetMapping("queryAllUserInfo")  // 使用 GET 请求
        public ResVo<List<UserInfoVo>> queryAllUserInfo() {
            List<User> userInfoList = userService.queryAllUserInfo();
            return ResVo.success(UserConverter.toUserInfoDTOList(userInfoList));
        }
    }
    
    
  • service层接口UserService
    package com.zzx.service;
    
    import com.zzx.entity.User;
    
    import java.util.List;
    
    
    public interface UserService {
    
        List<User> queryAllUserInfo();
    }
    
  • 服务层实现类UserServiceImpl
    package com.zzx.service.impl;
    
    
    import com.zzx.entity.User;
    import com.zzx.mapper.UserMapper;
    import com.zzx.service.UserService;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService {
        @Resource
        private UserMapper userMapper;
    
        @Override
        public List<User> queryAllUserInfo() {
            return userMapper.queryAllUserInfo();
        }
    }
    
  • mapper层UserMapper
    package com.zzx.mapper;
    
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.zzx.entity.User;
    
    import java.util.List;
    
    public interface UserMapper extends BaseMapper<User> {
        List<User> queryAllUserInfo();
    }
    
    
  • Mapper 层 SQL 映射配置文件UserMapper.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.zzx.mapper.UserMapper">
        <!-- 声明标签写sql语句  crud  select  insert update  delete
              每个标签对应接口的一个方法!  方法的一个实现!
              注意:mapper接口不能重载!!! 因为mapper.xml无法识别! 根据方法名识别!
         -->
        <!-- 查询所有用户 -->
        <select id="queryAllUserInfo" resultType="com.zzx.entity.User">
            SELECT id, user_name, pass_word, age, gender FROM user_info
        </select>
    
    </mapper>
    

2.1.7 跨域资源共享

  • 由于毛毛张这个代码还有前端代码,涉及到和前端交互,还需要做一个跨域资源共享的配置,毛毛张没有使用@CrossOrigin,而是通过拦截器的方式实现的:
    package com.zzx.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class CorsInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            response.setHeader("Access-Control-Allow-Origin", "*");  // 允许所有来源
            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
            response.setHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization");
            response.setHeader("Access-Control-Allow-Credentials", "true");
    
            // 处理OPTIONS请求
            if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
                response.setStatus(HttpServletResponse.SC_OK);
                return false;  // 返回false,表示不再执行后续的Controller方法
            }
    
            return true;  // 继续执行其他拦截器或Controller方法
        }
    }
    
    
  • 配置类:
    package com.zzx.config;
    
    import com.zzx.interceptor.CorsInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 注册 CORS 拦截器
            registry.addInterceptor(new CorsInterceptor())
                    .addPathPatterns("/**")  // 拦截所有路径
                    .excludePathPatterns("/login", "/error");  // 排除登录和错误页面
        }
    }
    

2.1.8 启动类

  • 启动类SpringbootHicariCpDemoApplication
    package com.zzx;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan("com.zzx.mapper")
    public class SpringbootHicariCpDemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootHicariCpDemoApplication.class, args);
        }
    
    }
    

2.1.9 测试

  • 启动后端程序,可以在浏览器中输入http://localhost:8080/user/queryAllUserInfo,返回结果如下则表示后端代码正确无误:
    image-20250125120253993

完整的后端代码已上传至毛毛张Github仓库:https://github.com/zzxrepository/SpringBootTutorial/tree/master/springboot-mysql/springboot-HicariCP-demo

2.2 前端代码

3 配置详解

3.1 常见配置

  • 下面是HicariCP连接池常见的配置:

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/springboot?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: abc123
        type: com.zaxxer.hikari.HikariDataSource # 指定数据源类型为 HikariDataSource
        hikari: # Hikari 连接池的详细配置
          pool-name: HikariCP       # 连接池名称
          minimum-idle: 5 # 最小空闲连接数
          idle-timeout: 600000  # 空闲连接超时时间(毫秒)
          maximum-pool-size: 10 # 连接池的最大大小
          auto-commit: true # 是否自动提交事务
          max-lifetime: 1800000 # 连接的最大生命周期(毫秒)
          connection-timeout: 30000 # 连接超时时间(毫秒)
          connection-test-query: SELECT 1 # 测试连接的 SQL 语句
    
  • 上面配置主要分为两部分:一部分是数据源配置,一部分是数据库连接池配置

3.2 数据源配置解析

  • 下面部分属于数据源配置:
    spring:
      datasource:
        # 数据库连接URL,指定数据库地址、端口、库名以及连接参数
        url: jdbc:mysql://localhost:3306/springboot?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&serverTimezone=UTC
        # MySQL 8.0 及以上需要 cj,5.7 以下可去掉 cj
        driver-class-name: com.mysql.cj.jdbc.Driver 
        # 数据库的用户名和密码
        username: root
        password: abc123
        # 指定使用的数据库连接池实现
        type: com.zaxxer.hikari.HikariDataSource
    
  • 配置解析:
    • url:定义数据库连接的详细信息,包括:
      • localhost:3306(服务器地址和端口)
      • springboot(数据库名称)
      • 连接参数
    • driver-class-name:指定数据库连接的驱动,MySQL 8.x使用 com.mysql.cj.jdbc.Driver
    • usernamepassword:提供访问数据库的身份凭证。
    • type:指定数据库连接池的实现,配置为 com.zaxxer.hikari.HikariDataSource,表示使用 HikariCP 作为数据库连接池。如果需要使用 Druid,应将其替换为 com.alibaba.druid.pool.DruidDataSource
  • url连接参数详解:
    • useUnicode=true&characterEncoding=UTF-8
      • 确保数据库与应用之间的字符编码一致,防止乱码问题。
      • 推荐使用 utf8mb4 以支持更多字符(如表情符号)。
    • serverTimezone=UTC
      • 指定数据库服务器的时区,避免时间处理错误。
      • 可选值:
        • UTC:世界标准时间,比北京时间(CST)早 8 小时。
        • Asia/Shanghai:中国标准时间(推荐)。
      • 说明:MySQL 6.0 及以上的 com.mysql.cj.jdbc.Driver 需要显式指定时区,否则可能导致时区不匹配的异常。
    • useSSL=false
      • 指定是否启用 SSL 连接。
      • 在生产环境(如 Linux 服务器部署)通常关闭 SSL 连接,以减少额外的安全配置。
      • 建议:在公共网络或云数据库环境中建议开启 SSL。
    • autoReconnect=true&failOverReadOnly=false
      • autoReconnect=true:允许 JDBC 驱动在连接意外断开时自动重连(已弃用,推荐使用连接池)。
      • failOverReadOnly=false:防止发生故障转移时连接错误地设置为只读模式。
    • allowPublicKeyRetrieval=true
      • 允许客户端从 MySQL 服务器检索公钥,以进行密码验证。
      • 安全建议:生产环境下尽量避免使用该参数,建议启用 SSL 进行安全通信。
    • zeroDateTimeBehavior=convertToNull
      • 当数据库中的日期时间值为 0000-00-00 00:00:00 时,转换为 null,避免 Java 应用程序在处理无效日期值时出错。
    • rewriteBatchedStatements=true
      • 启用批量执行优化,提高批量 INSERTUPDATE 的执行效率。
      • 适用于大数据量操作,不适用于 SELECT 查询。

3.3 连接池配置详解

  • 下面是关于连接池的配置部分:

    spring:
      datasource:
        hikari:
          # 连接池名称
          pool-name: HikariCP
          # 最小空闲连接数
          minimum-idle: 5
          # 空闲连接超时时间(毫秒)
          idle-timeout: 600000
          # 连接池的最大大小
          maximum-pool-size: 10
          # 是否自动提交事务
          auto-commit: true
          # 连接的最大生命周期(毫秒)
          max-lifetime: 1800000
          # 连接超时时间(毫秒)
          connection-timeout: 30000
          # 测试连接的 SQL 语句
          connection-test-query: SELECT 1
    
  • 关于HikariCP连接池的详细配置解析如下:

属性 描述 构造器默认值 默认配置 validate 之后的值 validate 重置
autoCommit 自动提交从池中返回的连接 true true
connectionTimeout 等待来自池的连接的最大毫秒数 30000 (30秒) 30000 如果小于 250 毫秒,则重置为 30 秒
idleTimeout 连接允许在池中闲置的最长时间 600000 (10分钟) maxLifetime 影响,条件不符可能重置为 0 如果设置小于 10 秒则重置为 10 秒
maxLifetime 池中连接最长生命周期 1800000 (30分钟) 1800000 如果小于 30 秒则重置为 30 分钟
connectionTestQuery 如果支持 JDBC4,建议不设置此属性 null null
minimumIdle 池中维护的最小空闲连接数 10 10 小于 0 或大于 maxPoolSize 则重置为 maxPoolSize
maximumPoolSize 池中最大连接数,包括闲置和使用中的连接 10 10 如果小于 1,则重置为默认 10 或 minIdle 的值
metricRegistry 指定 Codahale/Dropwizard MetricRegistry 的实例 null null
healthCheckRegistry 指定 Dropwizard HealthCheckRegistry 的实例 null null
poolName 连接池的用户定义名称,主要用于日志和 JMX 管理 null HikariPool-1
initializationFailTimeout 控制池是否在初始化连接失败时快速失败 1 1
isolateInternalQueries 是否在独立事务中隔离内部池查询 false false
allowPoolSuspension 是否允许通过 JMX 暂停和恢复池 false false
readOnly 从池中获取的连接是否默认处于只读模式 false false
registerMbeans 是否注册 JMX 管理 Bean(MBeans) false false
catalog 设置默认 catalog default null
connectionInitSql 在新连接创建后执行的 SQL 语句 null null
driverClassName JDBC URL 解析失败时指定驱动程序类名称 null null
transactionIsolation 连接的默认事务隔离级别 null null
validationTimeout 连接测试活动的最大时间 5000 (5秒) 5000 如果小于 250 毫秒,则重置为 5 秒
leakDetectionThreshold 检测潜在的连接泄漏的时间阈值 0 0 必须 > 0 且 ≥ 2 秒,且不能大于 maxLifetime
dataSource 设置要包装的数据源实例 null null
schema 设置默认 schema default null
threadFactory 设置用于创建池线程的 ThreadFactory 实例 null null
scheduledExecutor 设置用于池内部任务的 ScheduledExecutorService 实例 null null

4 底层源码解析

  • 前面毛毛张介绍了HikariCP连接池的配置,下面毛毛张通过分析源码的方式来介绍以下为什么说SoringBoot2.x之后默认使用的连接池是HikariCP,从SpringBoot自动初始化配置 和 默认的数据源 两个角度理解。

4.1 SpringBoot自动初始化配置

  • 找到spring-boot-autocinfigure-2.7.6.jar依赖下面的org.springframework.boot.autoconfigure.jdbc
    image-20250125140727917
  • 关键代码如下:
    image-20250125140535976
  • 找到HikariCP数据源的配置:你可以发现,为了支持动态更新配置(基于MXBean),这里还设计了一层HikariConfigMXBean接口
    image-20250125141106379

4.2 默认的数据源

  • 首先,springboot-starter-jdbc中默认加载了HikariCP
    image-20250125143209056
    image-20250125143333154
  • 其次,在配置初始化或者加载时都是第一个被加载的
    image-20250125144401033

参考文献


都看到这了,不妨一键三连再走吧!

🌈欢迎和毛毛张一起探讨和交流!
联系方式点击下方个人名片


网站公告

今日签到

点亮在社区的每一天
去签到