首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从 "连接池混战" 到 Druid 实战:一文看透数据库连接池的最优解

从 "连接池混战" 到 Druid 实战:一文看透数据库连接池的最优解

作者头像
果酱带你啃java
发布2026-04-14 10:45:09
发布2026-04-14 10:45:09
220
举报

在 Java 开发中,数据库连接池是提升系统性能的关键组件,却也是最容易被忽视的技术点。当你的应用从 "能跑" 迈向 "能扛",连接池的选择和配置将直接决定系统的稳定性与吞吐量。本文将深入对比主流数据库连接池的优劣,重点剖析 Druid 的核心优势,并通过完整实战案例带你掌握 Druid 的高级用法,让你的数据库操作从 "蹒跚学步" 到 "健步如飞"。

一、数据库连接池:性能优化的 "隐形引擎"

数据库连接是一种昂贵的资源,建立连接需要经过 TCP 握手、权限验证等一系列操作,耗时通常在毫秒级甚至更高。在高并发场景下,频繁创建和关闭连接会导致严重的性能瓶颈。

数据库连接池的核心思想是 "池化管理":预先创建一定数量的数据库连接,用户请求时直接从池中获取连接,使用完毕后归还而非关闭。这种机制能显著减少连接建立和关闭的开销,提升系统响应速度和并发处理能力。

1.1 连接池的核心工作原理

连接池的工作流程可以概括为以下几个步骤:

代码语言:javascript
复制

连接池的关键参数包括:

  • 初始连接数(initialSize):连接池启动时创建的连接数量
  • 最大连接数(maxTotal):连接池允许创建的最大连接数量
  • 最大空闲连接数(maxIdle):连接池允许保持的最大空闲连接数
  • 最小空闲连接数(minIdle):连接池保持的最小空闲连接数
  • 最大等待时间(maxWaitMillis):当没有可用连接时,客户端最大等待时间

1.2 主流连接池横向对比

目前 Java 生态中常用的数据库连接池有:Druid、HikariCP、C3P0、DBCP2,它们各有特点:

特性

Druid

HikariCP

C3P0

DBCP2

性能

优秀

优秀

一般

良好

监控能力

强大(内置监控页面)

基础

扩展性

高(支持插件机制)

稳定性

功能丰富度

高(防 SQL 注入等)

社区活跃度

配置复杂度

HikariCP 以极致的性能著称,代码精简,适合对性能有极高要求且不需要复杂功能的场景;C3P0 历史悠久但性能一般,逐渐被淘汰;DBCP2 作为 Apache 的产品,稳定性不错但功能相对简单;而 Druid 则在性能、功能和监控方面取得了很好的平衡,尤其适合企业级应用。

二、为什么 Druid 是企业级应用的首选?

Druid 是阿里巴巴开源的数据库连接池,不仅具备出色的性能,还提供了丰富的监控和扩展功能,被誉为 "数据库连接池中的瑞士军刀"。

2.1 Druid 的核心优势

  1. 卓越的性能表现:Druid 在性能测试中表现优异,尤其在高并发场景下,其性能接近甚至超过以性能著称的 HikariCP。
  2. 全方位的监控体系:内置强大的监控功能,可监控连接池状态、SQL 执行情况、慢查询等,支持 Web 页面查看。
  3. 丰富的扩展功能:提供 SQL 拦截、SQL 格式化、防 SQL 注入等实用功能,支持多种插件扩展。
  4. 强大的容错能力:具备连接有效性检测、自动重连等机制,提高系统稳定性。
  5. 完善的日志系统:详细记录连接的创建、获取、释放等过程,便于问题排查。

2.2 Druid 的架构设计

Druid 的架构设计采用了分层和插件化思想,核心组件包括:

代码语言:javascript
复制
  • DruidDataSource:连接池的核心类,负责管理整个连接池的生命周期
  • 连接池核心:处理连接的创建、分配、回收等核心操作
  • 监控系统:收集和展示连接池及 SQL 执行的监控数据
  • SQL 解析器:解析 SQL 语句,提供 SQL 格式化、语义分析等功能
  • 插件系统:支持多种插件,实现功能的灵活扩展

三、Druid 实战:从入门到精通

接下来,我们将通过一个完整的实战案例,展示如何在 Spring Boot 项目中集成和使用 Druid,包括基本配置、监控设置、高级特性等。

3.1 环境准备

我们将使用以下技术栈:

  • JDK 17
  • Spring Boot 3.2.0
  • MyBatis-Plus 3.5.5
  • Druid 1.2.20
  • MySQL 8.0.33
  • Maven 3.9.6

3.2 项目初始化

首先创建一个 Spring Boot 项目,在 pom.xml 中添加必要的依赖:

代码语言:javascript
复制
<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>druid-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>druid-demo</name>
    <description>Demo project for Druid</description>

    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <druid.version>1.2.20</druid.version>
        <mysql.version>8.0.33</mysql.version>
        <lombok.version>1.18.30</lombok.version>
        <fastjson2.version>2.0.32</fastjson2.version>
        <guava.version>32.1.3-jre</guava.version>
        <springdoc.version>2.1.0</springdoc.version>
    </properties>

    <dependencies>
        <!-- Spring Boot 核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 数据库连接 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-3-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>

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

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>

        <!-- FastJSON2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>

        <!-- Guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>

        <!-- Swagger3 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>

        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
代码语言:javascript
复制

3.3 数据库设计

创建一个简单的用户表用于演示:

代码语言:javascript
复制
CREATE DATABASE IF NOT EXISTS druid_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE druid_demo;

CREATE TABLE IF NOT EXISTS `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `age` int DEFAULT NULL COMMENT '年龄',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
代码语言:javascript
复制

3.4 配置文件

在 application.yml 中配置 Druid 和数据源信息:

代码语言:javascript
复制
spring:
  datasource:
    druid:
      # 数据库连接信息
      url: jdbc:mysql://localhost:3306/druid_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

      # 连接池配置
      initial-size: 5                # 初始连接数
      max-active: 20                 # 最大连接数
      min-idle: 5                    # 最小空闲连接数
      max-wait: 60000                # 获取连接时的最大等待时间(毫秒)

      # 连接有效性检测配置
      validation-query: SELECT 1     # 验证连接有效性的SQL
      test-on-borrow: false          # 从连接池获取连接时是否检测有效性
      test-on-return: false          # 归还连接时是否检测有效性
      test-while-idle: true          # 空闲时是否检测有效性
      time-between-eviction-runs-millis: 60000  # 检测间隔时间(毫秒)
      min-evictable-idle-time-millis: 300000    # 连接最小空闲时间(毫秒)

      # 日志配置
      filters: stat,wall,log4j2      # 启用的过滤器:监控统计、防火墙、日志
      filter:
        stat:
          enabled: true
          log-slow-sql: true         # 记录慢SQL
          slow-sql-millis: 1000      # 慢SQL阈值(毫秒)
          merge-sql: true            # 合并同类SQL
        wall:
          enabled: true
          config:
            drop-table-allow: false  # 禁止删除表操作

      # 监控配置
      stat-view-servlet:
        enabled: true                # 启用监控页面
        url-pattern: /druid/*        # 监控页面访问路径
        login-username: admin        # 监控页面登录用户名
        login-password: admin        # 监控页面登录密码
        reset-enable: false          # 是否允许重置监控数据
      web-stat-filter:
        enabled: true                # 启用Web监控
        url-pattern: /*              # 监控所有URL
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"  # 排除的资源

# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml
  type-aliases-package: com.example.druiddemo.entity
  configuration:
    map-underscore-to-camel-case: true  # 下划线转驼峰
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl  # 日志实现

# 日志配置
logging:
  level:
    com.example.druiddemo: debug
    com.alibaba.druid: info

# 服务器配置
server:
  port: 8080
代码语言:javascript
复制

3.5 实体类

创建 User 实体类:

代码语言:javascript
复制
package com.example.druiddemo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
import java.time.LocalDateTime;

/**
 * 用户实体类
 *
 * @author ken
 */
@Data
@TableName("user")
public class User {
    /**
     * 主键ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 创建时间
     */
    @TableField(value = "create_time", fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(value = "update_time", fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
代码语言:javascript
复制

3.6 Mapper 接口

创建 UserMapper 接口:

代码语言:javascript
复制
package com.example.druiddemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.druiddemo.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * 用户Mapper接口
 *
 * @author ken
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
代码语言:javascript
复制

3.7 服务层

创建 UserService 接口和实现类:

代码语言:javascript
复制
package com.example.druiddemo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.druiddemo.entity.User;
import java.util.List;

/**
 * 用户服务接口
 *
 * @author ken
 */
public interface UserService extends IService<User> {
    /**
     * 创建用户
     *
     * @param user 用户信息
     * @return 创建的用户
     */
    User createUser(User user);

    /**
     * 根据ID获取用户
     *
     * @param id 用户ID
     * @return 用户信息
     */
    User getUserById(Long id);

    /**
     * 获取所有用户
     *
     * @return 用户列表
     */
    List<User> getAllUsers();

    /**
     * 更新用户
     *
     * @param user 用户信息
     * @return 是否更新成功
     */
    boolean updateUser(User user);

    /**
     * 删除用户
     *
     * @param id 用户ID
     * @return 是否删除成功
     */
    boolean deleteUser(Long id);
}
代码语言:javascript
复制

代码语言:javascript
复制
package com.example.druiddemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.druiddemo.entity.User;
import com.example.druiddemo.mapper.UserMapper;
import com.example.druiddemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.util.List;

/**
 * 用户服务实现类
 *
 * @author ken
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    /**
     * 创建用户
     *
     * @param user 用户信息
     * @return 创建的用户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public User createUser(User user) {
        log.info("创建用户: {}", user);
        if (ObjectUtils.isEmpty(user)) {
            throw new IllegalArgumentException("用户信息不能为空");
        }
        baseMapper.insert(user);
        return user;
    }

    /**
     * 根据ID获取用户
     *
     * @param id 用户ID
     * @return 用户信息
     */
    @Override
    public User getUserById(Long id) {
        log.info("根据ID获取用户: {}", id);
        if (ObjectUtils.isEmpty(id)) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        return baseMapper.selectById(id);
    }

    /**
     * 获取所有用户
     *
     * @return 用户列表
     */
    @Override
    public List<User> getAllUsers() {
        log.info("获取所有用户");
        return baseMapper.selectList(new QueryWrapper<>());
    }

    /**
     * 更新用户
     *
     * @param user 用户信息
     * @return 是否更新成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateUser(User user) {
        log.info("更新用户: {}", user);
        if (ObjectUtils.isEmpty(user) || ObjectUtils.isEmpty(user.getId())) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        return baseMapper.updateById(user) > 0;
    }

    /**
     * 删除用户
     *
     * @param id 用户ID
     * @return 是否删除成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteUser(Long id) {
        log.info("删除用户: {}", id);
        if (ObjectUtils.isEmpty(id)) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        return baseMapper.deleteById(id) > 0;
    }
}

3.8 控制层

创建 UserController:

代码语言:javascript
复制
package com.example.druiddemo.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.druiddemo.entity.User;
import com.example.druiddemo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.ObjectUtils;
import java.util.List;

/**
 * 用户控制器
 *
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户CRUD操作")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * 创建用户
     *
     * @param user 用户信息
     * @return 创建的用户
     */
    @PostMapping
    @Operation(summary = "创建用户", description = "添加新用户到系统")
    public ResponseEntity<User> createUser(
            @Parameter(description = "用户信息", required = true) @RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.ok(createdUser);
    }

    /**
     * 根据ID获取用户
     *
     * @param id 用户ID
     * @return 用户信息
     */
    @GetMapping("/{id}")
    @Operation(summary = "根据ID获取用户", description = "通过用户ID查询用户详情")
    public ResponseEntity<User> getUserById(
            @Parameter(description = "用户ID", required = true) @PathVariable Long id) {
        User user = userService.getUserById(id);
        if (ObjectUtils.isEmpty(user)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(user);
    }

    /**
     * 获取所有用户
     *
     * @return 用户列表
     */
    @GetMapping
    @Operation(summary = "获取所有用户", description = "查询系统中所有用户")
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }

    /**
     * 分页获取用户
     *
     * @param page 页码
     * @param size 每页数量
     * @return 分页用户列表
     */
    @GetMapping("/page")
    @Operation(summary = "分页获取用户", description = "分页查询系统中的用户")
    public ResponseEntity<IPage<User>> getUsersByPage(
            @Parameter(description = "页码", required = true) @RequestParam(defaultValue = "1") Integer page,
            @Parameter(description = "每页数量", required = true) @RequestParam(defaultValue = "10") Integer size) {
        Page<User> userPage = new Page<>(page, size);
        IPage<User> users = userService.page(userPage);
        return ResponseEntity.ok(users);
    }

    /**
     * 更新用户
     *
     * @param id 用户ID
     * @param user 用户信息
     * @return 更新结果
     */
    @PutMapping("/{id}")
    @Operation(summary = "更新用户", description = "根据ID更新用户信息")
    public ResponseEntity<Boolean> updateUser(
            @Parameter(description = "用户ID", required = true) @PathVariable Long id,
            @Parameter(description = "用户信息", required = true) @RequestBody User user) {
        user.setId(id);
        boolean updated = userService.updateUser(user);
        return ResponseEntity.ok(updated);
    }

    /**
     * 删除用户
     *
     * @param id 用户ID
     * @return 删除结果
     */
    @DeleteMapping("/{id}")
    @Operation(summary = "删除用户", description = "根据ID删除用户")
    public ResponseEntity<Boolean> deleteUser(
            @Parameter(description = "用户ID", required = true) @PathVariable Long id) {
        boolean deleted = userService.deleteUser(id);
        return ResponseEntity.ok(deleted);
    }
}
代码语言:javascript
复制

3.9 配置类

创建 MyBatis-Plus 分页插件配置类:

代码语言:javascript
复制
package com.example.druiddemo.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MyBatis-Plus配置类
 *
 * @author ken
 */
@Configuration
public class MyBatisPlusConfig {

    /**
     * 配置分页插件
     *
     * @return MybatisPlusInterceptor
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件,指定数据库类型为MySQL
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
代码语言:javascript
复制

3.10 启动类

创建项目启动类:

代码语言:javascript
复制
package com.example.druiddemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;

/**
 * Druid示例项目启动类
 *
 * @author ken
 */
@SpringBootApplication
@MapperScan("com.example.druiddemo.mapper")
@OpenAPIDefinition(
        info = @Info(
                title = "Druid实战示例API",
                version = "1.0",
                description = "Druid连接池实战示例项目的API文档"
        )
)
public class DruidDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DruidDemoApplication.class, args);
    }
}
代码语言:javascript
复制

3.11 测试接口

启动项目后,可以通过以下方式测试接口:

  1. Swagger 文档:访问 http://localhost:8080/swagger-ui/index.html 查看 API 文档并测试
  2. Druid 监控页面:访问 http://localhost:8080/druid/index.html,使用配置的用户名 admin 和密码 admin 登录

四、Druid 高级特性详解

4.1 监控系统深度剖析

Druid 提供了强大的监控功能,能够实时监控连接池状态、SQL 执行情况等关键指标。

4.1.1 主要监控指标
  • 数据源监控:连接数、活跃数、等待数、创建数、销毁数等
  • SQL 监控:SQL 执行次数、执行时间、平均耗时、慢 SQL 等
  • Web 监控:URL 访问次数、访问时间、会话数等
  • URI 监控:接口调用次数、响应时间等
4.1.2 自定义监控

除了内置监控,Druid 还支持自定义监控。例如,我们可以实现一个自定义的 StatFilter 来监控特定 SQL 的执行情况:

代码语言:javascript
复制
package com.example.druiddemo.filter;

import com.alibaba.druid.filter.FilterChain;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.proxy.jdbc.ConnectionProxy;
import com.alibaba.druid.proxy.jdbc.StatementProxy;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.alibaba.druid.stat.JdbcSqlStat;
import lombok.extern.slf4j.Slf4j;
import java.sql.SQLException;

/**
 * 自定义Druid监控过滤器
 *
 * @author ken
 */
@Slf4j
public class CustomDruidStatFilter extends StatFilter {

    @Override
    public void statementExecuteUpdateBefore(StatementProxy statement, String sql) throws SQLException {
        log.info("执行更新SQL前: {}", sql);
        super.statementExecuteUpdateBefore(statement, sql);
    }

    @Override
    public void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) throws SQLException {
        log.info("执行更新SQL后: {}, 影响行数: {}", sql, updateCount);
        super.statementExecuteUpdateAfter(statement, sql, updateCount);
    }

    @Override
    public void connectionCommitAfter(ConnectionProxy connection) throws SQLException {
        log.info("事务提交");
        // 打印当前连接池状态
        printDataSourceStats();
        super.connectionCommitAfter(connection);
    }

    /**
     * 打印数据源状态
     */
    private void printDataSourceStats() {
        DruidDataSourceStatManager.getDataSourceInstances().forEach(dataSource -> {
            log.info("数据源状态: 连接数={}, 活跃数={}, 等待数={}",
                    dataSource.getConnectCount(),
                    dataSource.getActiveCount(),
                    dataSource.getWaitThreadCount());
        });
    }
}
代码语言:javascript
复制

然后在配置类中注册这个过滤器:

代码语言:javascript
复制
package com.example.druiddemo.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.example.druiddemo.filter.CustomDruidStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.Collections;

/**
 * Druid配置类
 *
 * @author ken
 */
@Configuration
public class DruidConfig {

    /**
     * 配置数据源,添加自定义过滤器
     *
     * @return 数据源
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid")
    public DataSource druidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        // 添加自定义监控过滤器
        dataSource.setProxyFilters(Collections.singletonList(new CustomDruidStatFilter()));
        return dataSource;
    }
}
代码语言:javascript
复制

4.2 防 SQL 注入与安全配置

Druid 的 WallFilter 提供了 SQL 防火墙功能,能够有效防止 SQL 注入攻击。我们可以通过配置来限制特定类型的 SQL 操作:

代码语言:javascript
复制
spring:
  datasource:
    druid:
      filter:
        wall:
          enabled: true
          config:
            drop-table-allow: false  # 禁止删除表
            alter-table-allow: false  # 禁止修改表
            truncate-allow: false     # 禁止清空表
            comment-allow: true       # 允许注释
            multi-statement-allow: false  # 禁止多语句执行
            # 限制特定表的操作
            table-check: true
            tables:
              user:
                select-allow: true
                update-allow: true
                insert-allow: true
                delete-allow: false  # 禁止删除用户表数据
代码语言:javascript
复制

4.3 连接池参数调优指南

Druid 的性能很大程度上取决于参数配置,以下是一些关键参数的调优建议:

  1. 初始连接数(initialSize)
    • 建议设置为 CPU 核心数的 1-2 倍
    • 太小会导致系统启动后需要频繁创建连接
    • 太大会导致启动变慢,资源浪费
  2. 最大连接数(maxActive)
    • 建议根据系统并发量和数据库性能综合设置
    • 计算公式:maxActive = (平均并发数 × 每个请求平均使用连接时间) / 平均连接复用次数
    • 通常设置为 50-200,不宜过大(会增加数据库负担)
  3. 最小空闲连接数(minIdle)
    • 建议设置为 initialSize 的一半
    • 太小会导致频繁创建连接
    • 太大会导致资源浪费
  4. 最大等待时间(maxWait)
    • 建议设置为 1000-5000 毫秒
    • 太小会导致频繁出现获取连接超时
    • 太大可能隐藏系统问题
  5. 连接检测参数
    • test-while-idle: 建议设为 true,定期检测空闲连接
    • time-between-eviction-runs-millis: 建议设为 60000 毫秒(1 分钟)
    • min-evictable-idle-time-millis: 建议设为 300000 毫秒(5 分钟)

调优是一个迭代过程,建议通过 Druid 的监控功能观察系统运行状态,逐步调整参数以达到最佳性能。

4.4 Druid 与分布式事务

在分布式系统中,Druid 可以与分布式事务框架(如 Seata)配合使用,确保跨库事务的一致性。以下是一个简单的配置示例:

代码语言:javascript
复制
# Seata配置
seata:
  enabled: true
  application-id: druid-demo
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace:
代码语言:javascript
复制

在需要分布式事务的方法上添加 @GlobalTransactional 注解:

代码语言:javascript
复制
@GlobalTransactional(rollbackFor = Exception.class)
public boolean transfer(Long fromUserId, Long toUserId, BigDecimal amount) {
    // 扣减转出用户余额
    userAccountMapper.decreaseBalance(fromUserId, amount);
    // 增加转入用户余额
    userAccountMapper.increaseBalance(toUserId, amount);
    return true;
}
代码语言:javascript
复制

五、Druid 常见问题与解决方案

5.1 连接泄露问题

连接泄露是指获取的连接没有正确关闭,导致连接池中的连接逐渐被耗尽。Druid 提供了检测和解决连接泄露的机制:

代码语言:javascript
复制
spring:
  datasource:
    druid:
      remove-abandoned: true              # 启用连接泄露检测
      remove-abandoned-timeout: 300       # 泄露超时时间(秒)
      log-abandoned: true                 # 记录泄露日志
代码语言:javascript
复制

5.2 慢 SQL 问题

慢 SQL 会严重影响系统性能,Druid 可以记录慢 SQL 并提供分析:

代码语言:javascript
复制
spring:
  datasource:
    druid:
      filter:
        stat:
          log-slow-sql: true         # 记录慢SQL
          slow-sql-millis: 1000      # 慢SQL阈值(毫秒)
代码语言:javascript
复制

解决慢 SQL 的常见方法:

  1. 优化 SQL 语句,避免全表扫描
  2. 添加合适的索引
  3. 分页查询大数据集
  4. 避免在循环中执行 SQL

5.3 高并发下的性能问题

在高并发场景下,可能会出现连接池性能瓶颈,解决方案包括:

  1. 合理配置连接池参数(见 4.3 节)
  2. 使用连接池监控找出性能瓶颈
  3. 实现读写分离,分散数据库压力
  4. 添加缓存层,减少数据库访问

5.4 数据库连接超时问题

连接超时通常有以下几种原因及解决方案:

网络问题:检查网络连接,增加超时时间

代码语言:javascript
复制
spring:
  datasource:
    druid:
      connectionProperties: connectTimeout=3000;socketTimeout=60000
代码语言:javascript
复制

数据库负载过高:优化数据库,增加资源

连接池配置不当:调整 maxActive、maxWait 等参数

连接失效:启用连接检测机制

代码语言:javascript
复制
spring:
  datasource:
    druid:
      test-while-idle: true
      validation-query: SELECT 1
代码语言:javascript
复制

六、总结与展望

Druid 作为一款优秀的数据库连接池,凭借其卓越的性能、丰富的功能和强大的监控能力,成为企业级应用的首选。本文从连接池的基本原理出发,对比了主流连接池的特点,详细介绍了 Druid 的优势和使用方法,并通过完整的实战案例展示了如何在 Spring Boot 项目中集成和使用 Druid。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-11-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、数据库连接池:性能优化的 "隐形引擎"
    • 1.1 连接池的核心工作原理
    • 1.2 主流连接池横向对比
  • 二、为什么 Druid 是企业级应用的首选?
    • 2.1 Druid 的核心优势
    • 2.2 Druid 的架构设计
  • 三、Druid 实战:从入门到精通
    • 3.1 环境准备
    • 3.2 项目初始化
    • 3.3 数据库设计
    • 3.4 配置文件
    • 3.5 实体类
    • 3.6 Mapper 接口
    • 3.7 服务层
    • 3.8 控制层
    • 3.9 配置类
    • 3.10 启动类
    • 3.11 测试接口
  • 四、Druid 高级特性详解
    • 4.1 监控系统深度剖析
      • 4.1.1 主要监控指标
      • 4.1.2 自定义监控
    • 4.2 防 SQL 注入与安全配置
    • 4.3 连接池参数调优指南
    • 4.4 Druid 与分布式事务
  • 五、Druid 常见问题与解决方案
    • 5.1 连接泄露问题
    • 5.2 慢 SQL 问题
    • 5.3 高并发下的性能问题
    • 5.4 数据库连接超时问题
  • 六、总结与展望
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档