为什么这段代码没有关闭JDBC连接?(Java 7 Autocloseable意外行为)

13

使用Java 7u5版本,并使用try-with-resources结构,下面的代码似乎泄漏了JDBC连接

try (Connection connection = ..; PreparedStatement stmt = ..) {
    stmt.setString(..);
    return stmt.executeUpdate() > 0;
}
下面的代码按预期和意图工作:
int ret = 0;

try (Connection connection = ..; PreparedStatement stmt = ..) {
    stmt.setString(..);
    ret = stmt.executeUpdate();
}

return ret > 0;

看起来在第一种情况下,Connection.close()方法没有被调用。

我正在使用最新的MySQL连接器。这是意外行为,对吗?

测试

以下测试不会打印CLOSED

public class Test implements AutoCloseable {

public static void main(String[] args) throws Exception {
    System.out.println(doTest());
}

private static boolean doTest() throws Exception {
    try (Test test = new Test()) {
        return test.execute() > 0;
    }

}

private int execute() {
    return 1;
}

@Override
public void close() throws Exception {
    System.out.println("CLOSED");
}
}

奇怪的是,如果将execute()修改为return 0;,那么CLOSED被打印。

javap -p -c Test.class 输出

    Compiled from "Test.java"
public class Test implements java.lang.AutoCloseable {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #10                 // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: invokestatic  #27                 // Method doTest:()Z
       6: invokevirtual #31                 // Method java/io/PrintStream.println:(Z)V
       9: return

  private static boolean doTest() throws java.lang.Exception;
    Code:
       0: aconst_null
       1: astore_0
       2: aconst_null
       3: astore_1
       4: new           #1                  // class Test
       7: dup
       8: invokespecial #39                 // Method "<init>":()V
      11: astore_2
      12: aload_2
      13: invokespecial #40                 // Method execute:()I
      16: ifle          21
      19: iconst_1
      20: ireturn
      21: iconst_0
      22: aload_2
      23: ifnull        30
      26: aload_2
      27: invokevirtual #44                 // Method close:()V
      30: ireturn
      31: astore_0
      32: aload_2
      33: ifnull        40
      36: aload_2
      37: invokevirtual #44                 // Method close:()V
      40: aload_0
      41: athrow
      42: astore_1
      43: aload_0
      44: ifnonnull     52
      47: aload_1
      48: astore_0
      49: goto          62
      52: aload_0
      53: aload_1
      54: if_acmpeq     62
      57: aload_0
      58: aload_1
      59: invokevirtual #47                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      62: aload_0
      63: athrow
    Exception table:
       from    to  target type
          12    22    31   any
          30    31    31   any
           4    42    42   any

  private int execute();
    Code:
       0: iconst_1
       1: ireturn

  public void close() throws java.lang.Exception;
    Code:
       0: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #55                 // String CLOSED
       5: invokevirtual #57                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

我假设你在MySQL中看到了悬空连接?你看过这个SO线程吗?https://dev59.com/fW025IYBdhLWcg3w1ZoL - Zeleres
1
感谢@Zeleres的评论。没有跨线程共享连接,因此您在此处看到的连接的生命周期应该仅限于其声明的范围内。 - beefyhalo
1
我现在使用的计算机上没有安装Java 7,所以无法尝试,但是您的两个代码示例看起来应该完全相同。这里有一个尝试的方法-用实现Closeable接口的自定义类替换Connection和PreparedStatement。在您的自定义类中,记录close()方法的调用。在两个代码示例中都尝试一下,看看会发生什么。如果在两种情况下都调用了close(),那么问题就出在其他地方。 - GreyBeardedGeek
1
@Beefyhalo:private方法/字段通常不会被javap显示。您需要添加-p以获取doTest的(相对较大的)输出。 - Joachim Sauer
1
@Beefyhalo:你是用javac还是其他编译器(比如Eclipse)? - Joachim Sauer
显示剩余14条评论
5个回答

3
升级到最新版本的Eclipse(Juno)后,不再出现这种奇怪的行为。使用命令行编译和运行也很好用。我怀疑Eclipse Indigo使用了旧版的javac进行编译,并且没有对任何兼容性问题提出异议。

2

我使用JDK1.7.0_17时遇到了同样的问题。经过仔细排除,发现我的IntelliJ使用了AspectJ编译器。当我使用JDK的javac编译类后,它按预期工作。

我的同事已向AspectJ人员提出错误报告。他们计划在1.7.3版本中修复该问题。


1

这是Java 7u5的一个bug;去注册一个bug。 Java 7u4可以正常工作。

return test.execute() > 0;

对于 > 0 给出了错误的代码:

  13: invokespecial #40                 // Method execute:()I
  16: ifle          21
  19: iconst_1
  20: ireturn

那段代码和 javap 输出不匹配。一个调用了 executeUpdate,另一个调用了 execute() - Joachim Sauer

0

编辑

  1. Eclipse Juno 中,它运行得非常好。我不认为这是 Java 7 的 bug。但是在从 Eclipse Indigo 运行时无法工作。

  2. 命令行 也可以运行。

之前的回答

在以下情况下,我运行了您的程序并且它能够正常工作,我正在检查您的情况

情况1:

public class Test implements AutoCloseable {

    public int execute() {
        return 1;
    }

    @Override
    public void close() throws Exception {
        System.out.println("CLOSED");
    }
}



public class Test1 {

    public static void main(String[] args) throws Exception {

        try (Test test = new Test()) {
            System.out.println(test.execute() > 0);
        }

    }

}

输出:

true
CLOSED

第二种情况:

public class Test implements AutoCloseable {

    public static void main(String[] args) throws Exception {
        System.out.println(doTest());
    }

    private static boolean doTest() throws Exception {
        try (Test test = new Test()) {
            throw new ArrayIndexOutOfBoundsException("exc"); // just for testing
            //return test.execute() > 0;
        }

    }

    private int execute() {
        return 1;
    }

    @Override
    public void close() throws Exception {
        System.out.println("CLOSED");
    }
}

输出:

CLOSED
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: exc
    at com.aquent.rambo.auth.Test.doTest(Test.java:11)
    at com.aquent.rambo.auth.Test.main(Test.java:6)

第三种情况:这里很奇怪,但它能工作

public class Test implements AutoCloseable {

    public static void main(String[] args) throws Exception {
        System.out.println(doTest());
    }

    private static boolean doTest() throws Exception {
        try (Test test = new Test()) {
            boolean result = test.execute() > 0; // Change : result variable declared
            return result;
        }

    }

    private int execute() {
        return 1;
    }

    @Override
    public void close() throws Exception {
        System.out.println("CLOSED");
    }
}

输出:

CLOSED
true

-3
你尝试过在完成后关闭语句和/或连接吗?
此外,请确保在 finally 块中执行此操作:

2
这是Java 7中的语法糖 - 使用特殊的try语法跳过finally块。 - Eugen Martynov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接