繁体   English   中英

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

[英]Proper way of handling database connections in a medium sized web application

我目前正在维护一个中小型的 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 是 class 扩展 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;
    }
}

谢谢!

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

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

这就是我重构 class 的方式:

  1. 删除公共getConnection方法,如果它在Database之外使用 class 你真的有设计问题
  2. 删除commit方法。 我想这没有意义,因为connection.setAutoCommit(false)从未被调用,而且我没有看到rollback方法
  3. 删除实例变量connection ,而是在每次调用时获取一个连接
  4. 并在每次调用的finally块中正确关闭此连接

免责声明:目前不知道您的事务处理是如何工作的,所以我可能对#2 有误。

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

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

有趣的是这个应用程序是如何工作的:-)

为了回答您关于设计原则的问题,这个 object 本质上是一个 DAO object,它只是不使用命名约定,而且大型应用程序将有几个这些对象用于不同类型的数据(可能使用基本 DAO ZA8CFDE6331BD49EB2AC96FB66都继承自)。

广义的想法是,DAO 是处理数据库连接的中心位置,因此您不会在 Controller object 中拥有所有代码。

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

This seems to be a highly abstracted object that returns an arraylist of maps(key value pairs), that can be used for different datatypes which is then used in the Model objects or the datatypes to build java objects with the information returned.

对于简单的应用程序来说,这样做没有错。 但是,如果您的应用程序甚至比较复杂,您可能希望研究一个简单的框架,例如 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 安装中定义了一个连接池,并且您的应用程序正在使用它。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM