尝试/尝试使用资源和Connection,Statement和ResultSet关闭

时间:2014-03-26 19:55:01

标签: java correctness try-with-resources

我最近与我的教授讨论了如何处理基本的jdbc连接方案。假设我们想要执行两个查询,这就是他提出的建议

public void doQueries() throws MyException{
    Connection con = null;
    try {
        con = DriverManager.getConnection(dataSource);
        PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
        PreparedStatement s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

        rs.close();
        s2.close();
        s1.close();
    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't really do anything
        }
    }
}

我不喜欢这种方法,我有两个问题:

1.A)我认为,如果在我们做“其他事情”,或者在rs.close()s2.close()行中抛出任何异常,那么s1就不会方法结束时关闭。我是对的吗?

1.B)教授一直要求我明确关闭ResultSet(即使Statement语句明确说明它将关闭ResultSet)她说Sun推荐它。有没有理由这样做?

现在我认为这是同一件事的正确代码:

public void doQueries() throws MyException{
    Connection con = null;
    PreparedStatement s1 = null;
    PreparedStatement s2 = null;
    try {
        con = DriverManager.getConnection(dataSource);
        s1 = con.prepareStatement(updateSqlQuery);
        s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (s2 != null) {
                s2.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (s1 != null) {
                s1.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't do nothing
        }
    }
}

2.A)这段代码是否正确? (当方法结束时,是否保证所有都将被关闭?)

2.B)这是非常庞大和冗长的(如果有更多的陈述,它会变得更糟)没有使用try-with-resources是否有更短或更优雅的方法来做到这一点?

最后这是我最喜欢的代码

public void doQueries() throws MyException{
    try (Connection con = DriverManager.getConnection(dataSource);
         PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
         PreparedStatement s2 = con.prepareStatement(selectSqlQuery))
    {

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    }
}

3)这段代码是否正确?我认为我的教授不喜欢这种方式,因为没有明确关闭ResultSet,但是她告诉我她很好,只要在文档中很清楚所有关闭。您是否可以通过类似示例提供官方文档的任何链接,或者根据文档显示此代码没有问题?

6 个答案:

答案 0 :(得分:8)

TL;博士

  • 理论上,关闭语句会关闭结果集。
  • 在实践中,一些错误的JDBC驱动程序实现无法做到这一点,这是出了名的。因此,教练的建议是她从硬敲门学校那里学到的。除非您熟悉可能为应用程序部署的每个JDBC驱动程序的每个实现,否则请使用try-with-resources自动关闭JDBC工作的每个级别,例如语句和结果集。

使用try-with-resources语法

使用try-with-resources,您的所有代码都不是完全。在try-with-resources语法中,您可以在大括号之前在括号中声明并实例化ConnectionPreparedStatementResultSet

虽然您的上一个代码示例中未明确关闭ResultSet,但应在语句关闭时间接关闭 。但是如下所述,由于JDBC驱动程序错误,它可能被关闭。

AutoCloseable

实现AutoCloseable的任何此类对象都将自动调用其close方法。所以不需要那些finally条款。

对于阅读本文的人文专业,是的,Java团队拼错了“可关闭”。

您如何知道哪些对象可自动关闭,哪些不可自动关闭?查看他们的类文档,看看它是否将AutoCloseable声明为超级接口。相反,请参阅the JavaDoc page for AutoCloseable以获取所有捆绑子接口和实现类的列表(实际上是几十个)。例如,对于SQL工作,我们发现ConnectionStatementPreparedStatementResultSetRowSet都可以自动关闭,{{3}不是。

请参阅Oracle教程DataSource

代码示例

您的上一个代码示例已接近良好,但应该在try-with-resources语句中包装ResultSet以自动关闭。

引用The try-with-resources Statement JavaDoc:

  

当生成它的Statement对象关闭,重新执行或用于从多个结果序列中检索下一个结果时,ResultSet对象会自动关闭。

正如您的老师一直建议的那样,某些JDBC驱动程序存在严重缺陷,这些驱动程序无法实现JDBC规范的承诺,即ResultSet或{{1}时关闭Statement }} 关闭了。许多程序员养成了明确关闭每个PreparedStatement对象的习惯。

现在使用try-with-resources语法可以更轻松地完成这项额外任务。在实际工作中,您可能会尝试围绕所有ResultSet对象,例如AutoCloseable。所以我自己的观点是:为什么不把它作为尝试资源+其他?没有伤害,使您的代码更自我记录您的意图,并且如果您的代码遇到其中一个有缺陷的JDBC驱动程序,可能会有所帮助。 唯一的成本是一对parens ,假设你还有一个try-catch-else。

ResultSet所述,一起声明的多个ResultSet对象将以相反的顺序关闭。

提示:try-with-resources语法允许在最后声明的资源项上使用可选的分号。我将分号作为习惯包括在内,因为它能很好地读取,并且是一致的,并且有助于剪切和粘贴编辑。我将其添加到您的AutoCloseable行。

PreparedStatement s2

我认为这种工作有一种更优雅的语法,可能是在未来的编程语言中发明的。但是现在,我们已经尝试了资源,我确实很愉快地使用它。虽然try-with-resources并不是非常优雅,但它比旧语法有了很大的改进。

顺便说一下,Oracle建议使用public void doQueries() throws MyException{ // First try-with-resources. try ( Connection con = DriverManager.getConnection( dataSource ) ; PreparedStatement s1 = con.prepareStatement( updateSqlQuery ) ; PreparedStatement s2 = con.prepareStatement( selectSqlQuery ) ; ) { … Set parameters of PreparedStatements, etc. s1.executeUpdate() ; // Second try-with-resources, nested within first. try ( ResultSet rs = s2.executeQuery() ; ) { … process ResultSet } catch ( SQLException e2 ) { … handle exception related to ResultSet. } } catch ( SQLException e ) { … handle exception related to Connection or PreparedStatements. } } 实现来获取连接,而不是代码中看到的DataSource方法。在整个代码中使用DriverManager可以更轻松地切换驱动程序或切换到连接池。查看JDBC驱动程序是否提供DataSource的实现。

更新:Java 9

现在,在Java 9中,您可以在尝试使用资源之前初始化资源。请参阅Oracle Tutorial

答案 1 :(得分:5)

关于JDBC代码的有趣之处在于,您需要编写一个规范,而这些规范并不总是清楚您的实现是否合规。有许多不同的数据库和驱动程序,一些驱动程序比其他驱动程序更好。这往往会让人们在谨慎方面犯错误,建议明确关闭所有内容。你可以在这里只关闭连接。关闭resultSet只是为了安全起见很难争辩。您没有指出您在此处使用的数据库或驱动程序,我不想在有关驱动程序的假设中进行硬编码,这些假设可能对某些实现无效。

按顺序关闭事项确实会让您对可能引发异常并导致某些关闭被跳过的问题持开放态度。你是对此感到担心的。

请注意这是一个玩具示例。大多数实际代码使用连接池,其中调用close方法实际上并不关闭连接,而是返回到池的连接。因此,一旦您使用池,资源可能无法关闭。如果您想更改此代码以使用连接池,那么您至少必须返回并关闭语句。

此外,如果您反对这种详细程度,那么答案就是将代码隐藏在使用策略,resultSet映射器,预处理语句设置器等的可重用实用程序中。这一切都在之前完成,课程;你将在重塑Spring JDBC的道路上前进。

说到:Spring JDBC明确地关闭了所有内容(可能是因为它需要使用尽可能多的驱动程序,并且由于某些驱动程序没有表现良好而不想导致问题)

答案 2 :(得分:1)

这确实是尝试资源的主要动机。请参阅Java tutorials作为参考。你的教授已经过时了。如果要处理结果集问题,可以随时将其括在另一个try-with-resources语句中。

答案 3 :(得分:0)

您可以创建一个util类来处理这些资源的关闭。即 仅供参考我只是忽略了尝试关闭util类中的资源的SQLExceptions,但是根据您的需要,一旦完成关闭集合中的资源,您就可以记录或收集并抛出它们

public class DBUtil {
public static void closeConnections(Connection ...connections){
    if(connections != null ){
        for(Connection conn : connections){
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

public static void closeResultSets(ResultSet ...resultSets){
    if(resultSets != null ){
        for(ResultSet rs: resultSets){
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

public static void closeStatements(Statement ...statements){
    if(statements != null){
        for(Statement statement : statements){
            if(statement != null){
                try {
                    statement.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

}

然后从你的方法中调用它:

    public void doQueries() throws MyException {
    Connection con = null;
    try {
        con = DriverManager.getConnection(dataSource);
        PreparedStatement s1 = null;
        PreparedStatement s2 = null;
        try {
            s1 = con.prepareStatement(updateSqlQuery);
            s2 = con.prepareStatement(selectSqlQuery);

            // Set the parameters of the PreparedStatements and maybe do other things
            s1.executeUpdate();
            ResultSet rs = null;
            try {
                rs = s2.executeQuery();
            } finally {
                DBUtil.closeResultSets(rs);
            }
        } finally {
            DBUtil.closeStatements(s2, s1);
        }

    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        DBUtil.closeConnections(con);
    }
}

答案 4 :(得分:0)

我发现这是处理JDBC等资源的最佳解决方案。此方法通过利用最终变量提供不可变函数,并且仅在需要时声明和分配这些变量,它具有非常高效的CPU,并且在所有情况下都保证所有分配和打开的资源都是关闭的,无论状态如何例外。您正在使用的技术会留下间隙,如果不仔细实施以解决所有情况,可能会导致资源泄漏。如果始终遵循模式,此技术不允许资源泄漏:
1)分配资源
2)试试 3)使用资源
4)最后关闭资源

public void doQueries() throws MyException {
   try {
      final Connection con = DriverManager.getConnection(dataSource);
      try {
         final PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
         try {

            // Set the parameters of the PreparedStatements and maybe do other things

            s1.executeUpdate();

         } finally {
            try { s1.close(); } catch (SQLException e) {}
         }

         final PreparedStatement s2 = con.prepareStatement(selectSqlQuery);
         try {

            // Set the parameters of the PreparedStatements and maybe do other things

            final ResultSet rs = s2.executeQuery();
            try {

               // Do something with rs

            } finally {
               try { rs.close(); } catch (SQLException e) {}
            }
         } finally {
            try { s2.close(); } catch (SQLException e) {}
         }
      } finally {
         try { con.close(); } catch (SQLException e) {}
      }
   } catch (SQLException e) {
      throw new MyException(e);
   }
}

使用Java 7,您可以利用新的try -with-resources来进一步简化:新的try -with-resources遵循上述逻辑流程,因为它将保证所有资源都包含在with资源块中分配被关闭。抛出with资源块中抛出的任何异常,但仍会关闭那些分配的资源。此代码非常简化,如下所示:

public void doQueries() throws MyException {
   try (
      final Connection con = DriverManager.getConnection(dataSource);
      final PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
      final PreparedStatement s2 = con.prepareStatement(selectSqlQuery);
      final ResultSet rs = s2.executeQuery()) {

      s1.executeUpdate();

         // Do something with rs

   } catch (SQLException e) {
      throw new MyException(e);
   }
}

[编辑]:将rs分配移动到资源块中以显示最简单的实现。在实践中,这个简单的解决方案并不真正起作用,因为这样做效率不高。连接重用,因为建立连接是一项非常昂贵的操作。此外,此简单示例不会将查询参数分配给预准备语句。应该注意处理这些场景,因为资源块应该只包含赋值语句。为了描述这一点,我还添加了另一个例子

   public void doQueries() throws MyException {

      final String updateSqlQuery = "select @@servername";
      final String selecSqlQuery  = "select * from mytable where col1 = ? and col2 > ?";
      final Object[] queryParams  = {"somevalue", 1};

      try (final Connection con = DriverManager.getConnection(dataSource);
         final PreparedStatement s1 = newPreparedStatement(con, updateSqlQuery);
         final PreparedStatement s2 = newPreparedStatement(con, selectSqlQuery, queryParams);
         final ResultSet rs = s2.executeQuery()) {

         s1.executeUpdate();

         while (!rs.next()) {
            // do something with the db record.
         }
      } catch (SQLException e) {
         throw new MyException(e);
      }
   }

   private static PreparedStatement newPreparedStatement(Connection con, String sql, Object... args) throws SQLException
   {
      final PreparedStatement stmt = con.prepareStatement(sql);
      for (int i = 0; i < args.length; i++)
         stmt.setObject(i, args[i]);
      return stmt;
   }

答案 5 :(得分:0)

我更喜欢让 Java 自动关闭。所以当我必须为 ResultSet 设置值时,我会做这样的事情。

try (Connection conn = DB.getConn();
     PreparedStatement ps = conn.prepareStatement("SELECT * FROM x WHERE y = ?")
) {
    ps.setString(1, "yValue");
   
    try(ResultSet rs = ps.executeQuery()) {
        while(rs.next()) {
            ...
        }
    }
} catch (SQLException e) {
    e.printStackTrace(e);
    ...
}