我想尝试编写一个简约的连接池实现(出于好奇和学习)。能否请大家检查一下代码并提供反馈?

有两个类(类名超链接到代码):



com.amitcodes.dbcp.ConnectionPool:连接池实现

com.amitcodes.dbcp.PooledConnectionjava.sql.Connection的代理,旨在确保不会从客户端代码关闭从ConnectionPool借来的连接,而应将其交还给池。


TODO(基于评论评论):

这就是为什么代码评论很重要。到目前为止,我已根据代码检查注释添加了以下更改:


维护池中可用活动连接的数量(最好使用AtomicInteger

borrowConnection()应该确保在打开新的池连接之前没有可用的空闲连接(使用上面的点1)。
surrenderConnection应该回滚未完成的事务。它们可能会泄漏。
确认交出的连接与借用的连接相同。如果未进行此检查,则客户端可以将与db'foo'的连接移交给dbcp的dbcp

surrenderConnection()应注意回滚所有未清事务
包括验证()验证构造函数参数的方法


代码:

package com.amitcodes.dbcp;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ConnectionPool {

    private static final Logger logger = Logger.getLogger(ConnectionPool.class.getCanonicalName());

    private BlockingQueue<Connection> pool;
    /**
     * Maximum number of connections that the pool can have
     */
    private int maxPoolSize;
    /**
     * Number of connections that should be created initially
     */
    private int initialPoolSize;
    /**
     * Number of connections generated so far
     */
    private int currentPoolSize;

    private String dbUrl;
    private String dbUser;
    private String dbPassword;

    public ConnectionPool(int maxPoolSize, int initialPoolSize, String url, String username,
                          String password, String driverClassName) throws ClassNotFoundException, SQLException {

        if ((initialPoolSize > maxPoolSize) || initialPoolSize < 1 || maxPoolSize < 1) {
            throw new IllegalArgumentException("Invalid pool size parameters");
        }

        // default max pool size to 10
        this.maxPoolSize = maxPoolSize > 0 ? maxPoolSize : 10;
        this.initialPoolSize = initialPoolSize;
        this.dbUrl = url;
        this.dbUser = username;
        this.dbPassword = password;
        this.pool = new LinkedBlockingQueue<Connection>(maxPoolSize);

        initPooledConnections(driverClassName);

        if (pool.size() != initialPoolSize) {
            logger.log(Level.WARNING,
                       "Initial sized pool creation failed. InitializedPoolSize={0}, initialPoolSize={1}",
                       new Object[]{pool.size(), initialPoolSize});
        }

    }

    private void initPooledConnections(String driverClassName)
            throws ClassNotFoundException, SQLException {

        // 1. Attempt to load the driver class
        Class.forName(driverClassName);

        // 2. Create and pool connections
        for (int i = 0; i < initialPoolSize; i++) {
            openAndPoolConnection();
        }
    }

    private synchronized void openAndPoolConnection() throws SQLException {
        if (currentPoolSize == maxPoolSize) {
            return;
        }

        Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
        pool.offer(new PooledConnection(conn, this));
        currentPoolSize++;

        logger.log(Level.FINE, "Created connection {0}, currentPoolSize={1}, maxPoolSize={2}",
                   new Object[]{conn, currentPoolSize, maxPoolSize});
    }

    public Connection borrowConnection() throws InterruptedException, SQLException {

        if (pool.peek()==null && currentPoolSize < maxPoolSize) {
            openAndPoolConnection();
        }
        // Borrowing thread will be blocked till connection
        // becomes available in the queue
        return pool.take();
    }

    public void surrenderConnection(Connection conn) {
        if (!(conn instanceof PooledConnection)) {
            return;
        }
        pool.offer(conn); // offer() as we do not want to go beyond capacity
    }
}


com.amitcodes.dbcp.PooledConnection:仅相关部分在此处发布,并且锅炉-车牌代码已被删除。您可以在GitHub上查看完整的类。

package com.amitcodes.dbcp;

import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;

public class PooledConnection implements Connection {

    private Connection coreConnection;
    private ConnectionPool connectionPool;

    public PooledConnection(Connection coreConnection, ConnectionPool connectionPool) {
        this.connectionPool = connectionPool;
        this.coreConnection = coreConnection;
    }

    @Override
    public void close() throws SQLException {
        connectionPool.surrenderConnection(this);
    }

    /* ****************************************************************
     *                          Proxy Methods
     * ****************************************************************/
    @Override
    public Statement createStatement() throws SQLException {
        return coreConnection.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return coreConnection.prepareStatement(sql);
    }

    // SOME CODE SKIPPED
}


评论

假设某个客户调用了surrenderConnection(connection),但仍然保持对连接的引用,即使该连接返回了可能由我移交给另一个客户端的民意测验,他仍然可以继续调用诸如createStatement()和prepareStatement()之类的方法?我们将如何解决呢?

#1 楼

一些随机注释:


如果您是我,请检查Apache Commons Pool的源代码和API。您可以借用有关可能的错误,极端情况,有用的API调用,参数等的想法。
surrenderConnection应该回滚未完成的事务。它们可能会泄漏。
SLF4J具有比JUL更好的API。 (如果您不熟悉Logback,也可以查看Logback。)
恶意(或写得不好的客户端,具有多个池)可以使用surrenderConnection实例调用PooledConnection实例,而该实例不是由被称为ConnectionPool的实例创建的。

我将在构造函数/方法中检查null。用null打电话给他们有意义吗?如果没有,请检查并引发异常。 Guava中的checkNotNull是一个不错的选择。

this.connectionPool = 
    checkNotNull(connectionPool, "connectionPool cannot be null");


(另请参见:有效的Java,第二版,项目38:检查参数的有效性)



评论


\ $ \ begingroup \ $
太好了:)点#2和#4会引起严重的错误。将修复它们并尽快更新代码。
\ $ \ endgroup \ $
–阿米特·沙玛(Amit Sharma)
2014年1月25日7:41

\ $ \ begingroup \ $
我通常避免使用SLF4J之类的其他日志记录api,并使用JUL以避免其他库依赖项。 Java EE服务器还使用JUL来捕获和管理日志。如果您使用日志记录API,则它将成为应用程序的责任。
\ $ \ endgroup \ $
–Archimedes Trajano
2014年1月28日下午16:35

#2 楼

看起来借用连接代码无需先检查池中是否有可用连接即可创建新连接。大多数连接池不是这样工作的。您可能想要存储可用连接的数量(考虑使用AtomicInteger进行安全的并发访问),并在添加新连接之前进行检查。

除此之外,它看上去很牢固。但是,您的测试覆盖范围似乎很薄。我建议加强。单元测试是确保您的代码按预期执行的绝佳方法。

评论


\ $ \ begingroup \ $
我打开了一个快速修复程序以查看队列,然后打开一个新的池连接(内联代码)。将对使用AtomicInteger进行更多考虑并更新代码。感谢您的评论:)
\ $ \ endgroup \ $
–阿米特·沙玛(Amit Sharma)
2014年1月25日7:33



#3 楼

我在这里不明白这个概念。 ConnectionPoolPooledConnection的一部分吗?

我对游泳池的理解-您是一个装有100个物品的水桶,拿一个就用,用它放回池塘里。

我希望编写类似以下内容的内容:

ConnectionPool.getConnection();
ConnectionPool.releaseConnection(Connection c);


该池只有1个实例。 (我将为池使用私有构造函数和getInstance())

评论


\ $ \ begingroup \ $
将ConnectionPool设为单例(私有构造函数和单例)是一个坏主意。如果同一客户端希望有2个连接池来连接2个URL,该怎么办?
\ $ \ endgroup \ $
–avmohan
19 Mar 28 '19在20:59

\ $ \ begingroup \ $
在这种情况下,您应该从同一池中建立2个连接。您不应创建多个池。
\ $ \ endgroup \ $
–Ram B Gorre
19/12/6在5:20

\ $ \ begingroup \ $
如果应用程序需要同时连接到db1和db2,则连接不能在同一池中。连接不可互换
\ $ \ endgroup \ $
–avmohan
19/12/6在8:14

\ $ \ begingroup \ $
天哪。老兄,你没明白。池用于一个db。对于不同的数据库,应该有一个不同的池。在这种情况下,传递数据库信息时,可能需要一个池工厂才能将池对象移交给您。
\ $ \ endgroup \ $
–Ram B Gorre
19/12/7在9:08



\ $ \ begingroup \ $
我的意思是-使用私有构造函数使它成为真正的Singleton,并且所有这些都无法为每个db创建不同的池。因此,更好的选择是将其保留为非jvm-singleton。可以通过工厂或DI框架实例化它
\ $ \ endgroup \ $
–avmohan
19/12/7在16:53