【问题标题】:Method for SELECT in java: what to return and how to close resources?java中SELECT的方法:返回什么以及如何关闭资源?
【发布时间】:2021-02-15 10:15:42
【问题描述】:

我是 Java 新手。我正在尝试创建一个类,其中包含一些用于 Java 1.6 中 SQL 操作的实用方法,以用于一般用途。

我写了一个selectMethod 来获取数据库上SELECT 的结果。

问题:如果我的selectMethod 方法返回ResultSet 类型,那么当我调用该方法时,其相关资源(ResultSetStatement)将不可避免地保持打开状态:我不能从另一种方法关闭它们,因为它们已被创建到 selectMethod... 另一方面我不能在 selectMethod 中关闭它们,否则后者不会返回任何东西。

所以我的观点是: ==> 如何关闭资源?

我无法使用try-with-resource,因为我使用的是早期版本的 Java。

在类似的问题中,我还没有找到解决这个问题的“一般方法”。

解决方案:目前我知道的仅有两种方法:

A) 避免创建返回ResultSet 类型的selectMethod,而只创建一个在内部执行查询的方法,以及对查询结果的其他操作。然后将所有资源关闭到方法中。

例子:

public String selectMethod(String query, Connection conn) {
    Statement stmt = null;
    ResultSet rset = null;
    String myOutput = "";
    try {
        stmt = conn.PreparedStatement(query);
        rset = st.executeQuery();
        myOutput = rs.getString(2);   // the "particular task" I perform on the data retrieved
    } catch (SQLException e) {
        System.out.println(e);
    } finally {
        rset.close();
        stmt.close();
    }
    return myOutput;
}
...
...
// method call:
String myQuery = "SELECT colA FROM table_name WHERE table_id = 192837465";
String theDataINeeded = selectMethod(myQuery, myConn);
myConn.close();

A) 的缺点:我想要一个通用的 SQL 类,不限于特定任务...

B)selectMethod,将ResultSet 数据复制到CachedRowSet 并返回CachedRowSet

例子:

public CachedRowSet selectMethod(String query, Connection conn) {
    Statement stmt = null;
    ResultSet rset = null;
    CachedRowSetImpl crset = null;
    try {
        stmt = conn.PreparedStatement(query);
        rset = st.executeQuery();
        crset = new CachedRowSetImpl();
        crset.populate(rset);
    } catch (SQLException e) {
        System.out.println(e);
    } finally {
        rset.close();
        stmt.close();
    }
    return crset;
}
...
...
// method call:
String myQuery = "SELECT colA FROM table_name WHERE table_id = 192837465";
CachedRowSetImpl theDataINeeded = new CachedRowSetImpl();
theDataINeeded = selectMethod(myQuery, myConn);
myConn.close();

B) 的缺点:我担心在选择多行时会耗尽内存。我无法使用LIMIT... OFFSET... 进行分页查询,因为我的数据库版本低于Oracle 12g,并且我不想进行查询操作以插入row_number() between ... and ...。我希望我的实用程序可以处理任何类型的查询。

有人知道其他解决方案吗?

提前致谢。

【问题讨论】:

  • 考虑阅读诸如 Hibernate 之类的 ORM 工具,它可以让您通过使用 objects 与数据库进行交互,而不是像您一样使用笨重的语句在上面做。
  • 很抱歉,我的笨拙的陈述让您烦恼,因为我说过我是 Java 新手。谢谢你的建议。
  • 我的经验法则是“不要关闭不属于你的东西”。因此,在您的方法中,语句和结果集是“您的”,但连接不是 - 它是作为参数提供给您的,因此从技术上讲,关闭它不是您的方法的责任 - 这应该由创建最初连接。
  • 谢谢 :) 我编辑了删除连接关闭的问题。

标签: java resultset cachedrowset


【解决方案1】:

另一种选择是为下面的方法提供结果映射器;

public interface ResultMapper<T> {

    /**
     * Extract row from the columns of the {@link ResultSet}.
     * Implementors should just get the values for the columns and not call {@link ResultSet#next()} or {@link ResultSet#close()}
     *
     * @param rs the rs
     * @return the t
     */
    T extractRow(ResultSet rs);
}

//
// see ResultMapper
// Be aware that this method returns list of type <T>
public <T> List<T> selectMethod(final String query, final Connection conn, final ResultMapper<T> resultMapper) {
        final List<T> results = new LinkedList<>();

        Statement stmt = null;
        ResultSet rset = null;
        try {

            stmt = conn.createStatement();
            rset = stmt.executeQuery(query);
            while (rset.next()) {
                results.add(resultMapper.extractRow(rset));
            }
            return results;
        } catch (final SQLException e) {
            // handle sql exceprion
        } finally {
            try {
                rset.close();
                stmt.close();
                conn.close();
            } catch (SQLException throwables) {
                // log error while closing
            }
        }
        
        return results;
    }

由于您是 Java 新手,您可能需要查看 java generics

因此,根据您提供的示例,我们要提取字符串字段。您可以像这样定义结果映射器:

public class MyResultMapper implements ResultMapper<String> {
    @Override
    public String extractRow(final ResultSet rs) {
        return rs.getString(2);   // the "particular task" I perform on the data retrieved
    }
}

然后你可以像这样执行查询:

String output = SqlUtils.selectMethod(sql, conn, new MyResultMapper()).stream()
                .findFirst();

【讨论】:

  • 从内存消耗的角度来看,最后一行的 findFirst() 是无用的。您在selectMethod 中完成实现的方式意味着应用程序将首先从数据库加载所有结果并从中消耗所有内存,然后它会丢弃结果列表中的所有内容以返回它的第一个元素。
  • 我知道这在这个用例中效率不高。此外,selectMethod 也不一定必须返回 List&lt;T&gt;。但是,感谢您的提醒。 :)
  • 我正在对 OP 的这一行说:“B 的缺点):我害怕在选择多行时内存不足”。实现完整的实际列表将使应用程序选择许多行。
  • 我没听懂?您已经可以完全控制需要在ResultMapper 中使用ResultSet 完成的操作。您可能会使用所有结果集,甚至根本不使用...
  • 非常感谢 Ali Can 的回答。不幸的是,我认为这不是一个好方法:正如 Prokhorov 指出的那样,该方法会将所有结果加载到内存中,然后丢弃那些不需要的结果......字符串的情况只是一个例子,如果我想怎么办做一个SELECT * from TABLE?
【解决方案2】:

创建一个实现AutoClosable(或Closable——我不记得这些是什么时候引入Java的)的Result对象怎么样?

StatementResultSet 对象是 Result 实例的属性,而您的 selectMethod() 只是它的工厂。然后你可以这样做:

Connection connection = …
Result result = null;
String query = "…";
try
{
     result = selectMethod( query, connection );
     ResultSet resultSet = result.getResultSet();
     myOutput = resultSet.getString(2);   // the "particular task" I perform on the data retrieved
}
catch( …Exception e )
{
    // Handle the exception
    …
}
finally
{
    if( result != null ) result.close();
}

Result 类大致如下所示:

public final class Result implements Closeable
{
    private final Statement m_Statement;
    private final ResultSet m_ResultSet;

    public Result( final Statement statement, final ResultSet resultSet )
    {
        m_Statement = statement;
        m_ResultSet = resultSet;
    }

    public final ResultSet getResultSet() { return m_ResultSet; }

    @Override
    public final void close() throws SQLException
    {
        m_ResultSet.close();
        m_Statement.close();
    }
}

当然,我的错误处理很差,需要改进……

connection 不是你的,你不能关闭它……

resultSettry-catch-finally 块内的那个)只是m_ResultSet 持有的引用的副本,在Result 实例内。因此,对resultSet.close() 的调用是多余的(或已过时——甚至由于我糟糕的错误处理而变得危险)。

你的selectMethod() 看起来像这样:

public final Result selectMethod( String query, Connection connection ) throws SQLException
{
    Statement statement = connection.PreparedStatement( query );
    ResultSet resultSet = statement.executeQuery();
    return new Result( statement, resultSet );
}

【讨论】:

  • 谢谢!最后,我或多或少地遵循了你的方式。我不知道如何实现 AutoCloseable 或 Closeable ......我在自己的答案中解释了我做了什么(见下文)。
【解决方案3】:

根据tquadrat的回答,我找到了另一种关闭资源的方法,只需在SQLUtil类中定义属性:

public class SQLUtil {
    
    //attributes:
    Connection connection;
    ResultSet resultSet;
    Statement statement;

    public void connectToMyDB {
        // populate the connection attribute
        ...
    }
    
    public ResultSet selectMethod (String query) {
        // here I populate the statement and resultSet attribute
        ...
        return resultSet;
    }
    
    ...
    public void cleanUpRes () {
        if (resultSet != null) resultSet.close();
        if (statement != null) resultSet.close();
    }
}

然后,当我调用selectMethod 时,我将得到resultSet 而不将其存储在内存中。处理完之后,我会调用cleanUpRes()关闭它。

我看到的缺点:

1 - 只有一个resultSetSQLUtil 对象相关,所以如果我必须同时处理两个或更多查询,我必须实例化许多不同的SQLUtil 对象......也许使用一组resultSet 属性可以工作,而不仅仅是一个?顺便说一句,这超出了问题的范围:)

2 - 外部方法负责关闭资源

你怎么看?谢谢大家

【讨论】:

  • 我在回答中添加了更多内容,以便更容易理解我最初的意思。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-09
  • 2020-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-03
相关资源
最近更新 更多