2014-02-28 52 views
5

假设你有下面的代码:重复使用PreparedStatement时可能发生资源泄漏?

Connection connection = null; 
    PreparedStatement ps = null; 

    try { 
     Connection = connectionFactory.getConnection(); 

     ps = statement.prepareStamement(someQuery); 
     // execute and read and stuff 

     // now you want to use the ps again, since you don't want ps1, ps2, ps3, etc. 
     ps = statement.prepareStatement(someOtherQuery); // DOES THIS FORM A POTENTIAL LEAK? 
    } catch (a lot of exceptions) { 
     // process exceptions 
    } finally { 

     // close the resources (using util class with null-checks and everything) 
     SomeUtilClass.close(ps); 
     SomeUtilClass.close(connection); 
    } 

是重复使用PS变量潜在的泄漏?

如果是这样,我会讨厌声明多个这样准备好的语句(ps1,ps2,ps3等)。我应该如何重构这个?

想到任何人?

编辑

接收几个答案,指出这并不重要。我想指出的是,我正在体验开放式游标的开放时间过长,并且想知道这种模式是否可能与它有关。

我的想法是:

第一条语句被取消引用和GC'ed,那么,如何在第一PreparedStatement的获得在这个例子中关闭(数据库明智)?

回答

4

这不一定会在经典的“永久性”意义上造成泄漏。垃圾收集器将最终到达准备好的语句的第一个实例,并调用它的终结器。

但是,让垃圾收集器处理释放潜在的关键资源(如DB句柄)并不是一个好习惯:在重新使用准备好的语句变量或根本不重用变量之前,应该调用close方法。

try { 
    Connection = connectionFactory.getConnection(); 

    ps = statement.prepareStamement(someQuery); 
    // execute and read and stuff 

    // now you want to use the ps again, since you don't want ps1, ps2, ps3, etc. 
    // v v v v v v v v v v v 
    SomeUtilClass.close(ps); 
    //^^^^^^^^^^^
    ps = statement.prepareStatement(someOtherQuery); // DOES THIS FORM A POTENTIAL LEAK? 
} catch (a lot of exceptions) { 
    // process exceptions 
} finally { 

    // close the resources (using util class with null-checks and everything) 
    SomeUtilClass.close(ps); 
    SomeUtilClass.close(connection); 
} 
0

不,它不是。这是因为Java运行时足够明智,可以在将其分配给其他内容之前从ps中删除前一个对象的引用。

如果引用计数下降到零,则该对象是垃圾收集的候选对象,因此它的终结器将被调用并释放资源。

-1

只要连接正确关闭,就没有问题了。顺便说一句:在这种情况下,不需要明确地关闭(最后一个)语句,因为关闭Connection将会为您执行此操作(并关闭通过它获取的所有其他资源)。

UPDATE 这取决于你的“有点太长”的定义。关闭连接时,通过它分配的所有资源(包括所有语句,通过这些创建的可能的ResultSet等)将尽快释放。根据GC启动时间的不同,丢失的PreparedStatements(包括它们的ResultSet等)可能会提前关闭,但没有人能保证这一点。因此,如果您确实需要依赖于在超出范围时立即关闭的语句,那么您必须在创建新语句之前调用相应的关闭方法。

3

一般来说,这个可能是形成一个内存泄漏(非托管资源)。如果不希望陈述明显的情况,如果资源应该关闭,那么你应该关闭它。在你的例子中,你失去了对第一次准备好的陈述的引用,因此无法关闭它们。

泄漏引用的GC和终结器很可能会执行必要的步骤来重新获取所获得的任何非托管资源的内存,但是,最佳实践表明您应该是确定性地执行此步骤的人员。

在您的例子,为了关闭所有准备的语句,你可以使用一个可迭代集合,像这样:

Deque<PreparedStatement> statements = new ArrayDeque<PreparedStatement>(); 
try { 
    statements.addFirst(statement.prepareStamement(someQuery)); 
    PreparedStatement statement = statements.getFirst(); 
    .. 
}... 
finally { 
    // enumerate statements and close them. 
} 
+0

+1为队列的想法 – bknopper

0

再利用本身的一份声明变量不创建一个资源泄漏。这里的问题是,在将它重新分配给第二个语句之前,您没有关闭第一个语句。

你可以将每个准备好的语句包装在它自己的try/catch块中,并在finally块中关闭,所以你可以确信它在进入下一个之前会被关闭。

如果期望的逻辑是中止后面的查询,如果第一个失败,则可以嵌套try/catch块,内部块关闭准备语句,然后重新抛出异常或抛出新异常。但在这一点上,坦率地说,我认为宣布多个变量会更容易和更清楚。

通常情况下,重新使用变量只能获得很少的收益。你是否想过要节省内存?我不知道PreparedStatement对象占用了多少内存,但是我怀疑它超过了几十个字节。除非你拥有数千或数百万的数组,否则它不值得担心。如果你必须编写额外的代码来支持重新使用变量 - 就像一个额外的关闭语句 - 代码可能需要比额外变量更多的内存。重新使用变量通常会使代码变得不那么清晰,因为现在读者必须弄清楚“哦,现在它持有第四个查询”或其他什么。 (我曾经和一个很努力地重新使用变量的人一起工作,他会重复使用它们来完全不同的东西。就像在函数开始时,某个字符串变量可能会持有客户号码,但后来一半他用它来保存邮政编码当然,这使得任何名称没有意义,所以他称他所有的字符串变量s1,s2,s3,等等;所有整数i1,i2,i3等尝试阅读他的节目是一场噩梦。)