在中型Web应用程序中处理数据库连接的正确方法

时间:2011-07-08 23:53:45

标签: java jsp servlets jdbc websphere

我目前正在维护一个中小型的Java Web应用程序(仅使用普通的JSP / Servlet),这是一个实习生为公司内部使用而且我在连接方面遇到了一些麻烦。

有时候我们会发现错误,例如"声明已关闭"或"连接已关闭"然后整个应用程序将停止工作,服务器必须重新启动。

我没有很多经验,我没有任何人可以指导或教我最佳实践,设计模式等等,但我很确定这不是正确的这样做的方式。我读过DAL,DAO和DTO等内容。我们的应用程序没有这些。

整个Web应用程序(即servlet)基本上都填充了类似于以下内容的调用:

Database db = Database.getInstance();
db.execute("INSERT INTO SomeTable VALUES (a, b, c)");
db.execute("UPDATE SomeTable SET Col = Val");

SELECT是这样完成的:

ArrayList<Model> results = Model.fetch("SELECT * FROM SomeTable");

其中Model是一个扩展HashMap并在表中表示单行的类。

这是Database.java的代码,并且想知道是否有人可以指出明显错误的事情(我非常确定有很多),可以做的任何快速修复和最好的资源关于数据库连接/连接处理的实践。

package classes;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public final class Database {

    public static Database getInstance() {
        if (Database.instance == null) {
            Database.instance = new Database();
        }
        return Database.instance;
    }

    // Returns the results for an SQL SELECT query.
    public ArrayList<HashMap<String, Object>> fetch(String sql) {

        ArrayList<HashMap<String, Object>> results = new ArrayList<HashMap<String, Object>>();

        try {

            PreparedStatement stmt = this.connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
            ResultSet rs = stmt.executeQuery();
            this.doFetch(rs, results);
            stmt.close();

        } catch (SQLException e) {
            this.handleException(e, sql);
        }

        return results;
    }

    public ArrayList<HashMap<String, Object>> fetch(String sql, ArrayList<Object> parameters) {

        ArrayList<HashMap<String, Object>> results = new ArrayList<HashMap<String, Object>>();

        try {

            // Bind parameters to statement.
            PreparedStatement pstmt = this.connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
            for (int i=0; i<parameters.size(); i++) {
                pstmt.setObject(i+1, parameters.get(i));
            }

            ResultSet rs = pstmt.executeQuery();
            this.doFetch(rs, results);
            pstmt.close();

        } catch (SQLException e) {
            this.handleException(e, sql, parameters);
        }

        return results;
    }

    public int execute(String sql) {
        int result = 0;
        try {
            Statement stmt = this.connection.createStatement();
            result = stmt.executeUpdate(sql);
            stmt.close();
        } catch (SQLException e) {
            this.handleException(e, sql);
        }
        return result;
    }

    public int execute(String sql, ArrayList<Object> parameters) {
        int result = 0;
        try {
            PreparedStatement pstmt = this.connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
            for (int i=0; i<parameters.size(); i++) {
                if (parameters.get(i) == null) {
                    pstmt.setNull(i+1, java.sql.Types.INTEGER);
                } else {
                    pstmt.setObject(i+1, parameters.get(i));
                }
            }
            result = pstmt.executeUpdate();
            pstmt.close();
        } catch (SQLException e) {
            this.handleException(e, sql, parameters);
        }
        return result;
    }

    public void commit() {
        try {
            this.connection.commit();
        } catch (SQLException e) {
            System.out.println("Failed to commit transaction.");
        }
    }

    public Connection getConnection() {
        return this.connection;
    }


    private static Database instance;
    private static DataSource dataSource = null;
    private Connection connection;

    private Database() {
        this.connect();
        this.execute("SET SCHEMA " + Constant.DBSCHEMA);
    }

    private void connect() {
        Connection connection = null;
        if (dataSource == null) {
            try {
                InitialContext initialContext = new InitialContext();
                dataSource = (DataSource)initialContext.lookup(
                        Constant.DEPLOYED ? Constant.PROD_JNDINAME : Constant.TEST_JNDINAME);
            } catch (NamingException e) {
                e.printStackTrace();
            }
        }
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        this.connection = connection;
    }

    // Fetches the results from the ResultSet into the given ArrayList.

    private void doFetch(ResultSet rs, ArrayList<HashMap<String, Object>> results) throws SQLException {
        ResultSetMetaData rsmd = rs.getMetaData();

        ArrayList<String> cols = new ArrayList<String>();           
        int numCols = rsmd.getColumnCount();

        for (int i=1; i<=numCols; i++) {
            cols.add(rsmd.getColumnName(i));
        }

        while (rs.next()) {
            HashMap<String, Object> result = new HashMap<String, Object>();
            for (int i=1; i<=numCols; i++) {
                result.put(cols.get(i-1), rs.getObject(i));
            }
            results.add(result);
        }

        rs.close();
    }

    private void handleException(SQLException e, String sql) {
        System.out.println("SQLException " + e.getErrorCode() + ": " + e.getMessage());
        System.out.println("Statement: " + sql);
        ExceptionAdapter ea = new ExceptionAdapter(e);
        ea.setSQLInfo(e, sql);
        throw ea;
    }

    private void handleException(SQLException e, String sql, ArrayList<Object> parameters) {
        if (parameters.size() < 100) {
            System.out.println("SQLException " + e.getErrorCode() + ": " + e.getMessage());
            System.out.println("PreparedStatement: " + sql.replace("?", "[?]"));
            System.out.println("Parameters: " + parameters.toString());
        }
        ExceptionAdapter ea = new ExceptionAdapter(e);
        ea.setSQLInfo(e, sql, parameters);
        throw ea;
    }
}

谢谢!

4 个答案:

答案 0 :(得分:13)

该类永远不会关闭连接:this.connection.close()。由于Database Singleton ,因此应用程序不会使用连接池(数据源)。所有传入请求只使用一个连接。

经验法则:每个方法获得一个连接(可能是每个SQL语句)。 dataSource.getConnection()并不昂贵。

这就是我重构课程的方法:

  1. 删除公开getConnection方法,如果它在Database类之外使用,则确实存在设计问题
  2. 删除commit方法。我认为它没有意义,因为connection.setAutoCommit(false)从未被调用过,我看不到rollback方法
  3. 删除实例变量connection,而不是每次调用获得连接
  4. 并在每次通话的finally块中正确关闭此连接
  5. 免责声明:不知道您的交易处理目前是如何运作的,所以我可能错了#2。

    获取连接的方法的示例代码:

    Connection c = null;
    try {
        c = this.dataSource.getConnection();
        c.executeStatement("select * from dual");
    } catch (SQLException e) {
        // handle...
    } finally {
        closeConnection(c);
    }
    

    有趣的是这个应用程序可以工作: - )

答案 1 :(得分:1)

对于简单的应用程序来说,这样做是没有错的。但是,如果您的应用程序甚至是中等复杂的,您可能需要研究一个简单的框架,例如iBatis。

我肯定会做的一些事情。首先,当抛出异常时,应用程序可能会泄漏连接,这与语句的关闭方式有关。所有关闭语句都应该在finally块中移动。

所以而不是:

try {
            Statement stmt = this.connection.createStatement();
            result = stmt.executeUpdate(sql);
            stmt.close();
        } catch (SQLException e) {
            this.handleException(e, sql);
        }

请改为:

Statement stmt = null;
try {
        stmt = this.connection.createStatement();
        result = stmt.executeUpdate(sql);
    } catch (SQLException e) {
        this.handleException(e, sql);
    } finally {
        if (stmt != null) stmt.close();
    }

另一件事是我会确保您为数据源使用数据库连接池。如果您在Tomcat中运行它,希望在tomcat安装中定义了一个连接池,并且您的应用程序正在使用它。

编辑:再次查看代码之后,我也没有看到实际关闭数据库连接的位置。这可能就是你用完连接的原因。你需要在Database类中添加一个close方法,它调用connection.close()。并确保在完成查询后调用它。再次,在try / finally块中。

答案 2 :(得分:1)

您正以非常不安全的方式使用JDBC连接。它可以从多个线程访问,并且不是线程安全的。这是一个Web应用程序,多个请求可以同时来自不同的用户。这是一个小小的奇迹,你的应用程序不会更频繁地崩溃。您可以使用多种策略来解决这个问题。您可以将连接存储在ThreadLocal或堆栈中。如果要在堆栈上保持连接,则必须在每个方法调用中打开和关闭它们。为了获得一些性能,您必须使用连接池。无论如何,连接池都不会受到伤害。

答案 3 :(得分:1)

要回答关于设计原则的问题,这个对象本质上是一个DAO对象,它只是不使用命名约定,大型应用程序也会为不同类型的数据提供多个这些对象(可能使用基础DAO)他们都继承自的对象。)

广泛的想法是,A DAO是处理数据库连接的中心位置,因此您没有在Controller对象中拥有所有代码。

除了其他人已经指出的缺点之外,这是由非常了解面向对象编程的人编写的一些可靠的代码。我的建议是从单例更改对象,并使用连接池管理数据库连接(正如其他人已经提到的那样)。

这似乎是一个高度抽象的对象,它返回一个map(键值对)的arraylist,可以用于不同的数据类型,然后在Model对象或数据类型中使用它来构建返回信息的java对象。