Spring Boot「26」数据库连接池

205 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情

Spring Boot 2.x 之后,默认使用 HikariCP 作为其默认的数据库连接池组件,在 Spring Boot 1.x 版本中,使用的事 Tomcat Connection Pool。 使用数据库连接池的出发点与线程池类似,通过将链接保存在连接池中来减少频繁创建、关闭连接的性能损失,以提升应用的整体性能。

简易的连接池实现

虽然业界已经存在许多非常成熟、性能优良的数据库连接池组件,例如 HikariCP、Tomcat Connection Pool、DBCP、C3P0 等。 从价值上看,重复造轮子是一件费力不讨好的事情; 但从学习连接池基本思想的角度上看,通过实现一个简易的连接池来探究数据库连接池的基本工作原理是非常有意义,且有价值的。

首先,我们定义一个连接池接口:

public interface MyConnectionPool {
    Connection getConnection();
    boolean releaseConnection(Connection connection);

    String getUrl();
    String getUser();
    String getPassword();
}

它虽然简单,但是基本上涵盖了一个数据库连接池应当支持的最基础操作。 之后,我们可以尝试实现它。

public class MyNaiveConnectionPoolImpl implements MyConnectionPool {
    private final static Integer DEFAULT_CONNECTION_POOL_SIZE = 10;
    static {

    }
    public static MyNaiveConnectionPoolImpl createPool(
            String url,
            String user,
            String password) throws SQLException{
        List<Connection> pool = new ArrayList<>(DEFAULT_CONNECTION_POOL_SIZE);
        for (int i = 0; i < DEFAULT_CONNECTION_POOL_SIZE; ++i) {
            pool.add(createConnection(url, user, password));
        }

        return new MyNaiveConnectionPoolImpl(url, user, password, pool);
    }

    public static MyNaiveConnectionPoolImpl createPool(
            String url,
            String user,
            String password,
            Integer maxSize) throws SQLException{

        final MyNaiveConnectionPoolImpl pool = createPool(url, user, password);
        pool.setMaxSize(maxSize);
        return pool;
    }

    private String url;
    private String user;
    private String password;

    private Integer maxSize;
    private List<Connection> connectionPool;
    private List<Connection> usedConnections = new ArrayList<>();

    public MyNaiveConnectionPoolImpl(String url, String user, String password, Integer maxSize, List<Connection> connectionPool) {
        this.url = url;
        this.user = user;
        this.password = password;
        this.connectionPool = connectionPool;
        this.maxSize = maxSize;
    }

    public MyNaiveConnectionPoolImpl(String url, String user, String password, List<Connection> connectionPool) {
        this(url, user, password, DEFAULT_CONNECTION_POOL_SIZE, connectionPool);
    }


    @Override
    public Connection getConnection() throws SQLException {
        if (this.connectionPool.isEmpty()) {
            if (usedConnections.size() < maxSize) {
                this.connectionPool.add(createConnection(url, user, password));
            }
        }
        final Connection conn = this.connectionPool.remove(connectionPool.size() - 1);
        this.usedConnections.add(conn);
        return conn;
    }

    private static Connection createConnection(String url, String user, String password) throws SQLException{
        return DriverManager.getConnection(url, user, password);
    }

    @Override
    public boolean releaseConnection(Connection connection) {
        this.connectionPool.add(connection);
        return this.usedConnections.remove(connection);
    }

    @Override
    public String getUrl() {
        return this.url;
    }

    @Override
    public String getUser() {
        return this.user;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    public void setMaxSize(Integer maxSize) {
        this.maxSize = maxSize;
    }
}

如何使用自定义的数据库连接池呢?

MyConnectionPool cp = MyNaiveConnectionPoolImpl.createPool("jdbc:h2:mem:testdb", "sa", "password", 20);

如何关闭连接池?

public void shutdown() throws SQLException{
    this.usedConnections.forEach(this::releaseConnection);
    for (Connection connection : this.connectionPool) {
    connection.close();
    }
    connectionPool.clear();
}

02-常用的连接池组件

业界常用的数据库连接池组件有:HikariCP、Apache Commons DBCP、C3P0、Tomcat Connection Pool 等; Spring Boot 2.x 版本以后,默认使用 HikariCP。 如果想使用其他的数据库连接池可以按照如下方法替换:

  1. 在 pom.xml 中排除 HikariCP 的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <exclusions>
        <exclusion>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  1. 增加其他的数据库连接池实现,例如 Tomcat Connection Pool(Spring Boot 1.x 默认的数据库连接池)
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jdbc</artifactId>
    <version>9.0.10</version>
</dependency>

运行 Spring Boot 应用,发现 DataSource 已经是org.apache.tomcat.jdbc.pool.DataSource类型。 关于 Spring Boot 查找数据库链接池的算法细节,可以参考官网 Supported Connection Pools

不同数据库连接池使用得 DataSource 类型(Spring Boot 会根据 classpath 下 jar 包自动创建 DataSource Bean):

  • HikariCP,com.zaxxer.hikari.HikariDataSource
  • Tomcat Connection Pool,org.apache.tomcat.jdbc.pool.DataSource
  • Commons DBCP2,org.apache.commons.dbcp2.BasicDataSource

如果要自定义各种设置,可以通过如下方式:

public class DataSourceConfig {
    private static HikariConfig hikariConfig = new HikariConfig();
    private static HikariDataSource hikariDataSource;

    static {
        hikariConfig.setJdbcUrl("jdbc:h2:mem:test");
        hikariConfig.setUsername("user");
        hikariConfig.setPassword("password");
        hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
        hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
        hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        hikariDataSource = new HikariDataSource(hikariConfig);
    }

    public static Connection getConnection() throws SQLException {
        return hikariDataSource.getConnection();
    }

    public DataSourceConfig() {
    }
}

上述 DataSource 也支持通过 Spring DataSourceBuilder 手动创建。 例如(等价于上述的写法):

@Bean
public DataSource dataSource() {
    DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
    dataSourceBuilder.driverClassName("org.h2.Driver");
    dataSourceBuilder.url("jdbc:h2:mem:test");
    dataSourceBuilder.username("user");
    dataSourceBuilder.password("password");
    return dataSourceBuilder.build();
    }

除此之外,还支持下述类型的 DataSource。

  • Spring JDBC,org.springframework.jdbc.datasource.SimpleDriverDataSource
  • H2 JdbcDataSource
  • PostgreSQL PGSimpleDataSource
  • C3P0,com.mchange.v2.c3p0.ComboPooledDataSource