Thursday, August 31, 2023

Quiz yourself: Try-with-resources and PreparedStatement database access

Quiz Yourself, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Exam, Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Learning

The best uses—and some limitations—of try-with-resources


Imagine that your system runs against a JDBC driver and a target database that fully and correctly implement the JDBC specifications, and your system has code that handles database resources properly by using the following try-with-resource statement:

String url = … //
var sql = "SELECT * FROM EMPLOYEE";
try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
  while (rs.next()) {
    … //
  }
}

You want to refine the selection so it returns not all rows but only rows that match a certain criteria. Therefore, you changed the SQL code to the following:

var sql = "SELECT * FROM EMPLOYEE WHERE NAME LIKE ?";

Which of the following will work correctly with the new SELECT statement while ensuring that the database resources are handled properly? Choose one.


A.

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql);
     ps.setString(1, "E%");
     ResultSet rs = ps.executeQuery()) {
  while (rs.next()) {
    … //
  }
}

B.

Connection conn = DriverManager.getConnection(url);
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "E%");
ResultSet rs = ps.executeQuery();
try (conn;ps;rs) {
  while (rs.next()) {
    … //
  }
}

C.

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, "E%");
  ResultSet rs = ps.executeQuery();
  while (rs.next()) {
    … //
  }
}

D.

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, "E%");
}
try (ResultSet rs = ps.executeQuery()) {
  while (rs.next()) {
    … //
  }
}

Answer. This question investigates the use of the try-with-resources construction and also uses a PreparedStatement.

First, look at the usage of the PreparedStatement. You have modified the SQL statement to the following form:

"SELECT * FROM EMPLOYEE WHERE NAME LIKE ?"

The question mark at the end is a placeholder for a value and, before the statement is executed, a value must be provided for this. The value can be supplied correctly by the method call ps.setString(1, "E%"), which is present in all the answers’ proposed code.

Two further issues remain. One is whether the structure of the code is valid. Another is whether the rather vague requirement, “ensuring that the database resources are handled properly,” is satisfied. This latter stipulation means that all the database resources must be closed reliably by the end of the code.

The try-with-resources structure was added in Java 7 to simplify the reliable closing of resources, which can be a bit messy using only the finally block provided before then. It closes all the resources that are declared inside the parentheses that immediately follow the keyword try. As a side note, the resources are closed in the opposite order in which they are listed inside the parentheses. However, there is a restriction that code placed inside the parentheses must be one of two types of elements, as follows:

◉ An initialized declaration of a final or effectively final variable of a type that implements the interface AutoCloseable.
◉ A simple reference to a final or effectively final variable of a type that implements the interface AutoCloseable that was initialized before the start of the try-with-resources structure. (This syntax feature was added in Java 9.)

From the description above, you can immediately determine that option A presents invalid syntax because it places the call to setString inside the resources block; therefore, option A is incorrect.

In option B, you can see that the syntax is correct. It uses the second syntax option described above; it simply names effectively final resources that were declared before entry into the try block. However, although the code will compile and run, it’s not a safe approach for closure of the resources. If an exception were to arise after some of the resources have been created but before entry to the try structure, no attempt would be made to close the resources. Because of this, option B is incorrect.

Option D is also incorrect. The code deals with three resources: a Connection, a PreparedStatement, and a ResultSet. There are two separate try-with-resource statements: one for the Connection and PreparedStatement and another for the ResultSet. There are two problems with this; one is syntactic, and one is semantic. The syntax problem is that resource variables declared and initialized inside the parentheses of a try-with-resources structure behave as formal parameters. Such variables have a scope that ends with the closing curly brace of the pair that immediately follows the closing parenthesis of the try structure. This is the same as with method formal parameters. Because of this, the ps variable is out of scope before the second try-with-resource begins with the following line:

try (ResultSet rs = ps.executeQuery()) {

Additionally, even if the scope problem didn’t cause the code to fail to compile, it’s semantically incorrect. The issue is that the PreparedStatement ps and the Connection conn that supports it would have been closed at the end of the first try-with-resources and would not work for use in the second try-with-resources.

What about option C? The syntax is valid, and the code properly handles all opened resources. That might seem surprising because the ResultSet is declared in the body of the try block instead of in the resource list inside the parentheses. Because of this, the resource is not automatically closed. However, this is allowed. The documentation for Statement—which is a parent interface of PreparedStatement—explicitly says

Note: When a Statement object is closed, its current ResultSet object, if one exists, is also closed.

Because the PreparedStatement is declared inside the parentheses, it will be automatically closed, and that guarantees that the ResultSet will be closed reliably, too. Therefore, option C is correct.

A couple of side notes are worth raising.

First, what’s that waffle in the opening of this question about “fully and correctly implement the JDBC specifications”? It turns out that not all databases and drivers implement the specification fully and correctly; some actually keep a ResultSet opened after closure of their Statement. Therefore, in practice option C might let you down. A more robust approach would be to use two try-with-resource blocks nested, as follows:

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, "E%");
  try (ResultSet rs = ps.executeQuery()) {
    while (rs.next()) {
      … //
    }
  }
}

The second side note is that the items in the resource list inside the parentheses of try-with-resources must be separated using semicolons, but also permitted (but not required) is a trailing semicolon. This is similar to the array literal syntax that allows a trailing comma. Such a feature might seem odd, but it can simplify reordering the elements (since the semicolons just move around with their statements and nothing will be missing). It can also simplify machine-generated code (because the machine can simply tack a semicolon on the end of each resource without having to decide if it’s the last one).

Conclusion. The correct answer is option C.

Source: oracle.com

Related Posts

0 comments:

Post a Comment