我是否正在使用JDBC连接池?

33

我试图确定是否在使用JDBC连接池。经过一些研究,实现似乎非常简单,事实上比普通连接更容易,因此我想验证。

这是我的连接类:

public class DatabaseConnection {

Connection conn = null;

public Connection getConnection() {

    BasicDataSource bds = new BasicDataSource();
    bds.setDriverClassName("com.mysql.jdbc.Driver");
    bds.setUrl("jdbc:mysql://localhost:3306/data");
    bds.setUsername("USERNAME");
    bds.setPassword("PASSWORD");

    try{
        System.out.println("Attempting Database Connection");
        conn = bds.getConnection();
        System.out.println("Connected Successfully");
    }catch(SQLException e){
        System.out.println("Caught SQL Exception: " + e);
    }
    return conn;
}

public void closeConnection() throws SQLException {
    conn.close();
}
这是真正的连接池吗?我在另一个类中使用连接,如下所示:
        //Check data against database.
    DatabaseConnection dbConn = new DatabaseConnection();
    Connection conn;
    ResultSet rs;
    PreparedStatement prepStmt;

    //Query database and check username/pass against table.
    try{
        conn = dbConn.getConnection();
        String sql = "SELECT * FROM users WHERE username=? AND password=?";
        prepStmt = conn.prepareStatement(sql);
        prepStmt.setString(1, user.getUsername());
        prepStmt.setString(2, user.getPassword());
        rs = prepStmt.executeQuery();

        if(rs.next()){ //Found Match.
            do{
                out.println("UserName = " + rs.getObject("username") + " Password = " + rs.getObject("password"));
                out.println("<br>");
            } while(rs.next());
        } else {
            out.println("Sorry, you are not in my database."); //No Match.
        }

        dbConn.closeConnection(); //Close db connection.

    }catch(SQLException e){
        System.out.println("Caught SQL Exception: " + e);
    }
4个回答

62

假设这里使用的是BasicDataSource来自于DBCP,那么是的,你正在使用连接池。但是,每次获取连接时都会重新创建另一个连接池。你实际上并没有从同一个池中池化连接。你需要在应用程序启动时仅创建一次连接池,并从中获取每个连接。你也不应将连接保持为实例变量。你还应该关闭连接、语句和结果集,以确保资源得到正确关闭,即使出现异常情况也是如此。Java 7的try-with-resources语句对此很有帮助,它会在try块完成后自动关闭资源。

以下是微调后的版本:

public final class Database {

    private static final BasicDataSource dataSource = new BasicDataSource();

    static {
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/data");
        dataSource.setUsername("USERNAME");
        dataSource.setPassword("PASSWORD");
    }

    private Database() {
        //
    }

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

}

(如果必要的话,可以将其重构为抽象工厂以改善可插入性)

private static final String SQL_EXIST = "SELECT * FROM users WHERE username=? AND password=?";

public boolean exist(User user) throws SQLException {
    boolean exist = false;

    try (
        Connection connection = Database.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL_EXIST);
    ) {
        statement.setString(1, user.getUsername());
        statement.setString(2, user.getPassword());

        try (ResultSet resultSet = preparedStatement.executeQuery()) {
            exist = resultSet.next();
        }
    }       

    return exist;
}

应该按照以下方式使用:

try {
    if (!userDAO.exist(username, password)) {
        request.setAttribute("message", "Unknown login. Try again.");
        request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response);
    } else {
        request.getSession().setAttribute("user", username);
        response.sendRedirect("userhome");
    }
} catch (SQLException e) {
    throw new ServletException("DB error", e);
}
在真正的Java EE环境中,您应该将DataSource的创建委托给容器/应用服务器,并从JNDI获取它。在Tomcat的情况下,例如,可以参见此文档:http://tomcat.apache.org/tomcat-6.0-doc/jndi-resources-howto.html

哇,谢谢你的改写。对于像我这样的新手来说,这是完美的帮助。 - ryandlf
这个解决方案是线程安全的吗?我需要调用connection.close()吗? - swapyonubuntu
1
@swapyonubuntu:使用新的Java7 try-with-resources语句,自动完成关闭操作。https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html - BalusC
@BalusC 为什么原始示例是“在每次连接获取时重新创建另一个连接池”?因为每次调用getConnection()时,它都会调用setDriver,setUrl.....等等?这些调用会重新创建另一个连接池吗? 就我所看到的您的示例,您将这些setDriver....放置在静态初始化程序中。 - user1169587
1
不行,因为它每次都调用了 new BasicDataSource() - BalusC

3

看起来并没有使用连接池。您应该将 DataSource 存储在 DatabaseConnection 中,而不是每次调用 getConnection() 时创建一个新的。getConnection() 应该返回 datasource.getConnection()。


2

看起来是使用了DBCP连接池。如果是这样的话,那么是的,它已经被池化了。下面是DBCP的默认池属性值。

/**
* The default cap on the number of "sleeping" instances in the pool.
* @see #getMaxIdle
* @see #setMaxIdle
*/
public static final int DEFAULT_MAX_IDLE  = 8;
/**
* The default minimum number of "sleeping" instances in the pool
* before before the evictor thread (if active) spawns new objects.
* @see #getMinIdle
* @see #setMinIdle
*/
public static final int DEFAULT_MIN_IDLE = 0;
/**
* The default cap on the total number of active instances from the pool.
* @see #getMaxActive
*/
public static final int DEFAULT_MAX_ACTIVE  = 8;

1

作为BalusC解决方案的后续,以下是一种实现,可在需要多个连接的应用程序中使用,或在不知道连接属性的公共库中使用...

import org.apache.commons.dbcp.BasicDataSource;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;

public final class Database {

    private static final ConcurrentHashMap<String, BasicDataSource> dataSources = new ConcurrentHashMap();

    private Database() {
        //
    }

    public static Connection getConnection(String connectionString, String username, String password) throws SQLException {

        BasicDataSource dataSource;

        if (dataSources.containsKey(connectionString)) {
            dataSource = dataSources.get(connectionString);
        } else {
            dataSource = new BasicDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl(connectionString);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSources.put(connectionString, dataSource);
        }

        return dataSource.getConnection();

    }

}

1
这个解决方案并不总是有效。尽管使用了ConcurrentHashMap,但它仍然存在竞态条件的问题。 - Carlos Caldas
@Carlos Caldas:我之前并不知道这个限制。你能否解释一下如何改进这个答案?我会很乐意更新答案。 - undefined
1
想象一下,在当前线程调用put()之前,另一个线程正在忙于创建数据源时,containsKey()返回false的情况。使用computeIfAbsent()代替containsKey()+put()可以解决这个问题。 - undefined
1
随着这个变化,只要你在其他地方不使用get()方法,ConcurrentHashMap就不再必要了。可以使用普通的HashMap。 - undefined
@BalusC 谢谢你的建议。我会找出那个旧项目,进行必要的修改,编译和测试,然后相应地更新我的答案。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接