专注Java教育14年 全国咨询/投诉热线:444-1124-454
赢咖4LOGO图
始于2009,口口相传的Java黄埔军校
首页 学习攻略 Java学习 Java连接池的简单介绍

Java连接池的简单介绍

更新时间:2022-09-02 11:46:06 来源:赢咖4 浏览1077次

Java连接池是什么?赢咖4小编来为大家解答。

概述

连接池是一种众所周知的数据访问模式。其主要目的是减少执行数据库连接和读/写数据库操作所涉及的开销。

在最基本的层面上, 连接池是一种数据库连接缓存实现 ,可以根据特定需求进行配置。

在本教程中,我们将讨论一些流行的连接池框架。然后我们将学习如何从头开始实现我们自己的连接池。

为什么是连接池?

当然,这个问题是修辞性的。

如果我们分析典型数据库连接生命周期中涉及的步骤顺序,我们就会明白为什么:

使用数据库驱动程序打开到数据库的连接

打开TCP 套接字以读取/写入数据

通过套接字读取/写入数据

关闭连接

关闭插座

很明显,数据库连接是相当昂贵的操作,因此,在每个可能的用例中都应该减少到最低限度(在边缘情况下,只是避免)。

这就是连接池实现发挥作用的地方。

通过简单地实现一个数据库连接容器,它允许我们重用许多现有的连接,我们可以有效地节省执行大量昂贵的数据库旅行的成本。这提高了我们的数据库驱动应用程序的整体性能。

JDBC 连接池框架

从实用的角度来看,考虑到已经可用的“企业就绪”连接池框架的数量,从头开始实施连接池是没有意义的。

从教学的角度来看,这是本文的目标,事实并非如此。

尽管如此,在我们学习如何实现一个基本的连接池之前,我们将首先展示一些流行的连接池框架。

1.Apache Commons DBCP

让我们从Apache Commons DBCP Component开始,这是一个功能齐全的连接池 JDBC 框架:

public class DBCPDataSource {   
    private static BasicDataSource ds = new BasicDataSource();    
    static {
        ds.setUrl("jdbc:h2:mem:test");
        ds.setUsername("user");
        ds.setPassword("password");
        ds.setMinIdle(5);
        ds.setMaxIdle(10);
        ds.setMaxOpenPreparedStatements(100);
    }    
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }    
    private DBCPDataSource(){ }
}

在这种情况下,我们使用带有静态块的包装类来轻松配置 DBCP 的属性。

以下是如何获得与DBCPDataSource类的池连接:

Connection con = DBCPDataSource.getConnection();

2.HikariCP

现在让我们看看HikariCP ,这是一个由Brett Wooldridge创建的闪电般快速的 JDBC 连接池框架

public class HikariCPDataSource {    
    private static HikariConfig config = new HikariConfig();
    private static HikariDataSource ds;    
    static {
        config.setJdbcUrl("jdbc:h2:mem:test");
        config.setUsername("user");
        config.setPassword("password");
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        ds = new HikariDataSource(config);
    }    
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }    
    private HikariCPDataSource(){}
}

同样,这里是如何获得与HikariCPDataSource类的池连接:

Connection con = HikariCPDataSource.getConnection();

3.C3P0

这篇评论的最后一篇是C3P0,这是一个由 Steve Waldman 开发的强大的 JDBC4 连接和语句池框架:

public class C3p0DataSource {
    private static ComboPooledDataSource cpds = new ComboPooledDataSource();
    static {
        try {
            cpds.setDriverClass("org.h2.Driver");
            cpds.setJdbcUrl("jdbc:h2:mem:test");
            cpds.setUser("user");
            cpds.setPassword("password");
        } catch (PropertyVetoException e) {
            // handle the exception
        }
    }    
    public static Connection getConnection() throws SQLException {
        return cpds.getConnection();
    }    
    private C3p0DataSource(){}
}

正如预期的那样,使用C3p0DataSource类获取池连接类似于前面的示例:

Connection con = C3p0DataSource.getConnection();

一个简单的实现

为了更好地理解连接池的底层逻辑,让我们创建一个简单的实现。

我们将从仅基于一个单一接口的松散耦合设计开始:

public interface ConnectionPool {
    Connection getConnection();
    boolean releaseConnection(Connection connection);
    String getUrl();
    String getUser();
    String getPassword();
}

ConnectionPool接口定义了基本连接池的公共 API 。

现在让我们创建一个提供一些基本功能的实现,包括获取和释放池连接:

public class BasicConnectionPool 
  implements ConnectionPool {
    private String url;
    private String user;
    private String password;
    private List<Connection> connectionPool;
    private List<Connection> usedConnections = new ArrayList<>();
    private static int INITIAL_POOL_SIZE = 10;    
    public static BasicConnectionPool create(
      String url, String user, 
      String password) throws SQLException { 
        List<Connection> pool = new ArrayList<>(INITIAL_POOL_SIZE);
        for (int i = 0; i < INITIAL_POOL_SIZE; i++) {
            pool.add(createConnection(url, user, password));
        }
        return new BasicConnectionPool(url, user, password, pool);
    }    
    // standard constructors    
    @Override
    public Connection getConnection() {
        Connection connection = connectionPool
          .remove(connectionPool.size() - 1);
        usedConnections.add(connection);
        return connection;
    }    
    @Override
    public boolean releaseConnection(Connection connection) {
        connectionPool.add(connection);
        return usedConnections.remove(connection);
    }    
    private static Connection createConnection(
      String url, String user, String password) 
      throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }   
    public int getSize() {
        return connectionPool.size() + usedConnections.size();
    }
    // standard getters
}

虽然非常幼稚,但BasicConnectionPool类提供了我们期望从典型连接池实现中获得的最小功能。

简而言之,该类基于存储10个连接的ArrayList初始化一个连接池,可以方便地重用。

还可以使用DriverManager类和Datasource实现创建 JDBC 连接。

由于保持连接数据库的创建不可知要好得多,我们在create()静态工厂方法中使用了前者。

在这种情况下,我们将方法放在BasicConnectionPool中 ,因为这是接口的唯一实现。

在具有多个ConnectionPool实现的更复杂的设计中,最好将其放置在接口中,从而获得更灵活的设计和更高水平的内聚。

这里要强调的最相关的一点是,一旦创建了池,就会从池中获取连接,因此无需创建新的。

此外,当一个连接被释放时,它实际上会返回到池中,因此其他客户端可以重用它。

没有与底层数据库的进一步交互,例如显式调用Connection 的 close()方法。

使用BasicConnectionPool类

正如预期的那样,使用我们的BasicConnectionPool类很简单。

让我们创建一个简单的单元测试并获得一个池化的内存H2连接:

@Test
public whenCalledgetConnection_thenCorrect() {
    ConnectionPool connectionPool = BasicConnectionPool
      .create("jdbc:h2:mem:test", "user", "password"); 
    assertTrue(connectionPool.getConnection().isValid(1));
}

进一步的改进和重构

当然,还有很多空间可以调整/扩展我们的连接池实现的当前功能。

例如,我们可以重构getConnection()方法并添加对最大池大小的支持。如果所有可用连接都被占用,并且当前池大小小于配置的最大值,则该方法将创建一个新连接。

我们还可以验证从池中获得的连接是否仍然存在,然后再将其传递给客户端:

@Override
public Connection getConnection() throws SQLException {
    if (connectionPool.isEmpty()) {
        if (usedConnections.size() < MAX_POOL_SIZE) {
            connectionPool.add(createConnection(url, user, password));
        } else {
            throw new RuntimeException(
              "Maximum pool size reached, no available connections!");
        }
    }
    Connection connection = connectionPool
      .remove(connectionPool.size() - 1);
    if(!connection.isValid(MAX_TIMEOUT)){
        connection = createConnection(url, user, password);
    }
    usedConnections.add(connection);
    return connection;
}

请注意,该方法现在抛出SQLException,这意味着我们还必须更新接口签名。

或者我们可以添加一个方法来优雅地关闭我们的连接池实例:

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

在生产就绪的实现中,连接池应该提供一堆额外的功能,例如跟踪当前正在使用的连接的能力,支持准备好的语句池等等。

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>