MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java JDBC的连接池配置与使用

2021-12-131.4k 阅读

一、JDBC 连接池简介

在传统的 JDBC 编程中,每次与数据库进行交互时,都会创建一个新的数据库连接。这意味着在高并发的应用场景下,频繁地创建和销毁数据库连接会带来巨大的性能开销。为了解决这个问题,JDBC 连接池应运而生。

JDBC 连接池是一种数据库连接管理技术,它预先创建一定数量的数据库连接,并将这些连接保存在一个池中。当应用程序需要与数据库进行交互时,从连接池中获取一个可用的连接,使用完毕后再将连接归还给连接池,而不是直接销毁。这样可以大大减少连接创建和销毁的开销,提高系统的性能和稳定性。

二、常用的 JDBC 连接池

  1. DBCP(Database Connection Pool):是 Apache 软件基金会 Jakarta 项目中的一个开源的 JDBC 连接池实现。它提供了一个高效、可靠的连接池管理机制,支持多种数据库。
  2. C3P0:是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3.0 和 JDBC2.0 的标准扩展。C3P0 具有自动回收空闲连接、自动检测连接有效性等功能。
  3. HikariCP:是一个高性能的 JDBC 连接池,它设计目标是实现快速、轻量级和简单易用。HikariCP 在性能上表现优异,尤其在高并发环境下。

三、DBCP 连接池的配置与使用

  1. 添加依赖 如果使用 Maven 构建项目,在 pom.xml 文件中添加 DBCP 的依赖:
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
  1. 配置 DBCP 连接池 可以通过代码方式配置 DBCP 连接池,如下所示:
import org.apache.commons.dbcp2.BasicDataSource;
import javax.sql.DataSource;
public class DBCPConfig {
    public static DataSource getDataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/yourdatabase");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        dataSource.setInitialSize(5);
        dataSource.setMaxTotal(10);
        dataSource.setMaxIdle(5);
        dataSource.setMinIdle(2);
        return dataSource;
    }
}

在上述代码中:

  • setUrl 方法设置数据库的 URL。
  • setUsernamesetPassword 方法设置数据库的用户名和密码。
  • setInitialSize 方法设置连接池启动时创建的初始连接数。
  • setMaxTotal 方法设置连接池中最多可以创建的连接数。
  • setMaxIdle 方法设置连接池中最大的空闲连接数。
  • setMinIdle 方法设置连接池中最小的空闲连接数。
  1. 使用 DBCP 连接池获取连接
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
public class DBCPExample {
    public static void main(String[] args) {
        DataSource dataSource = DBCPConfig.getDataSource();
        try (Connection connection = dataSource.getConnection()) {
            String sql = "SELECT * FROM users";
            try (PreparedStatement statement = connection.prepareStatement(sql);
                 ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    System.out.println("ID: " + resultSet.getInt("id") + ", Name: " + resultSet.getString("name"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,通过 DBCPConfig.getDataSource() 获取数据源,然后使用数据源的 getConnection 方法获取数据库连接。使用完毕后,通过 try - with - resources 语句自动关闭连接、语句和结果集。

四、C3P0 连接池的配置与使用

  1. 添加依赖pom.xml 文件中添加 C3P0 的依赖:
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
  1. 配置 C3P0 连接池 可以通过 XML 配置文件来配置 C3P0 连接池。在 src/main/resources 目录下创建一个 c3p0-config.xml 文件,内容如下:
<c3p0 - config>
    <default - config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/yourdatabase</property>
        <property name="user">root</property>
        <property name="password">password</property>
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="maxIdleTime">300</property>
    </default - config>
</c3p0 - config>

在上述配置文件中:

  • jdbcUrl 设置数据库的 URL。
  • userpassword 设置数据库的用户名和密码。
  • initialPoolSize 设置连接池启动时创建的初始连接数。
  • maxPoolSize 设置连接池中最多可以创建的连接数。
  • maxIdleTime 设置连接在池中最大的空闲时间(单位:秒)。
  1. 使用 C3P0 连接池获取连接
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Example {
    public static void main(String[] args) {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try (Connection connection = dataSource.getConnection()) {
            String sql = "SELECT * FROM users";
            try (PreparedStatement statement = connection.prepareStatement(sql);
                 ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    System.out.println("ID: " + resultSet.getInt("id") + ", Name: " + resultSet.getString("name"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,通过 ComboPooledDataSource 创建数据源,然后获取数据库连接进行数据库操作。

五、HikariCP 连接池的配置与使用

  1. 添加依赖pom.xml 文件中添加 HikariCP 的依赖:
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>4.0.3</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
  1. 配置 HikariCP 连接池 通过代码方式配置 HikariCP 连接池:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
public class HikariCPConfig {
    public static DataSource getDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/yourdatabase");
        config.setUsername("root");
        config.setPassword("password");
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(5);
        return new HikariDataSource(config);
    }
}

在上述代码中:

  • setJdbcUrl 设置数据库的 URL。
  • setUsernamesetPassword 设置数据库的用户名和密码。
  • setMaximumPoolSize 设置连接池中最多可以创建的连接数。
  • setMinimumIdle 设置连接池中最小的空闲连接数。
  1. 使用 HikariCP 连接池获取连接
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
public class HikariCPExample {
    public static void main(String[] args) {
        DataSource dataSource = HikariCPConfig.getDataSource();
        try (Connection connection = dataSource.getConnection()) {
            String sql = "SELECT * FROM users";
            try (PreparedStatement statement = connection.prepareStatement(sql);
                 ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    System.out.println("ID: " + resultSet.getInt("id") + ", Name: " + resultSet.getString("name"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,通过 HikariCPConfig.getDataSource() 获取数据源,然后获取数据库连接执行 SQL 查询。

六、连接池性能优化

  1. 合理设置连接池参数
  • 初始连接数(Initial Size):根据应用程序的启动负载和预计的并发访问量来设置。如果初始连接数设置过小,在高并发时可能会导致连接创建的延迟;如果设置过大,可能会浪费资源。
  • 最大连接数(Max Total / Max Pool Size):根据数据库服务器的硬件资源和应用程序的并发需求来设置。如果设置过大,可能会耗尽数据库服务器的资源;如果设置过小,可能无法满足高并发的需求。
  • 最小空闲连接数(Min Idle):确保在低负载时也有一定数量的空闲连接可用,避免频繁地创建和销毁连接。
  1. 连接有效性检测 连接池应该定期检测连接的有效性,以确保从连接池获取的连接是可用的。不同的连接池有不同的检测机制,例如 C3P0 可以通过 testConnectionOnCheckintestConnectionOnCheckout 属性来设置在连接归还和获取时检测连接的有效性;HikariCP 可以通过 connectionTestQuery 属性设置一个 SQL 查询来检测连接的有效性。

  2. 监控与调优 使用监控工具来实时监控连接池的运行状态,例如连接的使用情况、空闲连接数、等待连接的线程数等。根据监控数据来调整连接池的参数,以达到最佳的性能。

七、连接池在 Web 应用中的使用

  1. Servlet 应用 在 Servlet 应用中,可以将连接池配置为一个 Servlet 上下文监听器(ServletContextListener)。在监听器的 contextInitialized 方法中初始化连接池,在 contextDestroyed 方法中关闭连接池。
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
@WebListener
public class DBContextListener implements ServletContextListener {
    private DataSource dataSource;
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/yourdatabase");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        dataSource.setInitialSize(5);
        dataSource.setMaxTotal(10);
        sce.getServletContext().setAttribute("dataSource", dataSource);
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        BasicDataSource dataSource = (BasicDataSource) sce.getServletContext().getAttribute("dataSource");
        if (dataSource != null) {
            try {
                dataSource.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

在 Servlet 中,可以通过 ServletContext 获取连接池:

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
@WebServlet("/users")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        DataSource dataSource = (DataSource) getServletContext().getAttribute("dataSource");
        try (Connection connection = dataSource.getConnection()) {
            String sql = "SELECT * FROM users";
            try (PreparedStatement statement = connection.prepareStatement(sql);
                 ResultSet resultSet = statement.executeQuery()) {
                PrintWriter out = response.getWriter();
                response.setContentType("text/html");
                while (resultSet.next()) {
                    out.println("ID: " + resultSet.getInt("id") + ", Name: " + resultSet.getString("name") + "<br>");
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  1. Spring Boot 应用 在 Spring Boot 应用中,配置连接池非常简单。如果使用 HikariCP,只需要在 application.properties 文件中添加以下配置:
spring.datasource.url=jdbc:mysql://localhost:3306/yourdatabase
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.hikari.maximum - pool - size=10
spring.datasource.hikari.minimum - idle=5

Spring Boot 会自动配置 HikariCP 连接池,并且可以通过 DataSource 接口来获取连接。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
@RestController
public class UserController {
    @Autowired
    private DataSource dataSource;
    @GetMapping("/users")
    public String getUsers() {
        StringBuilder result = new StringBuilder();
        try (Connection connection = dataSource.getConnection()) {
            String sql = "SELECT * FROM users";
            try (PreparedStatement statement = connection.prepareStatement(sql);
                 ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    result.append("ID: ").append(resultSet.getInt("id")).append(", Name: ").append(resultSet.getString("name")).append("<br>");
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result.toString();
    }
}

八、常见问题及解决方法

  1. 连接泄漏 连接泄漏是指应用程序从连接池中获取连接后,没有正确地将连接归还给连接池,导致连接池中的连接不断减少,最终耗尽所有连接。
  • 原因:代码中没有正确关闭连接,例如在 try - catch 块中没有在 finally 块中关闭连接;或者在异常处理中没有正确处理连接关闭。
  • 解决方法:使用 try - with - resources 语句确保连接、语句和结果集能够正确关闭;在异常处理中添加关闭连接的逻辑。
  1. 连接超时 连接超时是指在获取连接时,等待时间超过了设定的超时时间。
  • 原因:连接池中的连接被耗尽,并且新的连接创建速度较慢;或者网络问题导致连接建立失败。
  • 解决方法:合理调整连接池的参数,增加最大连接数或初始连接数;检查网络配置,确保数据库服务器可访问。
  1. 数据库连接不稳定
  • 原因:数据库服务器负载过高、网络波动、数据库配置问题等。
  • 解决方法:优化数据库服务器的性能,例如调整数据库参数、增加硬件资源;检查网络稳定性;确保数据库配置正确,例如最大连接数设置合理。

通过对 JDBC 连接池的配置与使用的深入了解,我们可以在 Java 应用程序中有效地管理数据库连接,提高应用程序的性能和稳定性。不同的连接池有各自的特点和优势,我们可以根据项目的需求选择合适的连接池,并通过合理的配置和优化来充分发挥其性能。在实际开发中,要注意避免常见问题,确保连接池的正常运行。