Basic JDBC Syntax

This section will be a brief intro to JDBC. It will talk about the basics of JDBC, issues, caveats, and so forth. For more detailed information, visit the JDBC website (http://java.sun.com/products/jdbc/) which has many good resources, and which will always provide the most up to date information. Also, the API documentation included with your JDK has detailed information on specific classes, methods, and fields. Look for the java.sql package.

JDBC has classes to represent most of the basic pieces of a program's interaction with SQL—Connection, Statement, ResultSet, Blob, and Clob—all of which map directly to some concept in SQL. It also has helper classes, such as ResultSetMetaData and DatabaseMetaData, that represent meta-information. These are useful for when you'd like to get the capabilities of the database. They are also useful for getting the types of results returned by a query, either for debugging, or because you don't know about the data you are dealing with.

PostgreSQL's JDBC interface also provides classes to map to PostgreSQL's non-standard extensions to JDBC's SQL support, which are Fastpath, geometric types, native large objects, and a class that aids serialization of Java objects into the database.

Basic JDBC Usage

Example 12-2 used a Connection object, representing a physical connection to the database. Now you can use this Connection object to create Statement objects. Statement objects are JDBC's way of getting SQL statements to the database.

There are three main types of Statement objects: the base class Statement, the PreparedStatement, and the CallableStatement.

To create a Statement object, use the createStatement method as shown in Example 12-3:

Example 12-3. A JDBC Statement Object

Statement s = c.createStatement();

Example 12-3 creates a Statement object named s, from the Connection object c. You can now use this Statement object to execute queries and updates on the database.

There are two main methods in the Statement class that are important. The first is executeQuery. This method takes one argument, the SQL statement to be executed, and returns an object of type ResultSet, which is discussed later. This method is used for executing queries which will return a set of data back, for instance, a SELECT statement. The ResultSet object returned represents the data resulting from the query.

Example 12-4 retrieves some data from the booktown database:

Example 12-4. A Simple JDBC Select

Statement s = null;
try {
  s = c.createStatement();
} catch (SQLException se) {
  System.out.println("We got an exception while creating a statement:" +
                     "that probably means we're no longer connected.");
  se.printStackTrace();
  System.exit(1);
}
ResultSet rs = null;
try {
  rs = s.executeQuery("SELECT * FROM books");
} catch (SQLException se) {
  System.out.println("We got an exception while executing our query:" +
                     "that probably means our SQL is invalid");
  se.printStackTrace();
  System.exit(1);
}

int index = 0;

try {
  while (rs.next()) {
      System.out.println("Here's the result of row " + index++ + ":");
      System.out.println(rs.getString(1));
  }
} catch (SQLException se) {
  System.out.println("We got an exception while getting a result:this " +
                     "shouldn't happen: we've done something really bad.");
  se.printStackTrace();
  System.exit(1);
}

Example 12-4 creates a Statement object, and then uses that Statement object's executeQuery method to execute the query SELECT * FROM books. You get back a ResultSet, and use that ResultSet to print out some of the information you got back.

The other important method is executeUpdate. This method, again, takes one argument, which is the SQL statement to be executed. This statement, unlike the last one, is a statement which changes data in the database: for example, a CREATE, an INSERT, or an UPDATE statement. The difference is that this returns an int, and this corresponds to the number of records that were modified.

Example 12-5 uses the executeUpdate method to insert a new row into the books table.

Example 12-5. A Simple JDBC Insert

Statement s = null;
try {
  s = c.createStatement();
} catch (SQLException se) {
  System.out.println("We got an exception while creating a statement:" +
                     "that probably means we're no longer connected.");
  se.printStackTrace();
  System.exit(1);
}

int m = 0;

try {
  m = s.executeUpdate("INSERT INTO books VALUES " + 
                      "(41472, 'Practical PostgreSQL', 1212, 4)");
} catch (SQLException se) {
  System.out.println("We got an exception while executing our query:" +
                     "that probably means our SQL is invalid");
  se.printStackTrace();
  System.exit(1);
}

System.out.println("Successfully modified " + m + " rows.\n");

The ResultSet object is our primary interface for fetching information from the database. It has two main features. It can step through the set of rows returned, and it can return values for a specific column in that row. It works in a similar fashion to a standard Java Enumeration: it starts before the first element, and you use the next method to step through the rest of the elements.

next returns true if the ResultSet was able to step to the next results; that is to say, there are results to be read. The while loop in Example 12-4 will print out the first column of each of the rows returned. If no rows were returned, next will return false initially, representing this fact, and therefore nothing will be printed.

ResultSet can return values of all sorts of different types; Example 12-4 treats the first column as if it were a String. Fortunately, all standard SQL data types can be represented as String, so regardless of the type of the first column, you will be able to fetch the value of the first column and print it out. There are many other methods available on ResultSet, including methods for fetching all the various SQL data types and converting them to native Java types. Consult the API documentation on ResultSet for more information.

Using Advanced JDBC Features

As mentioned earlier, besides the basic Statement object, there are two additional types of statements available in JDBC: PreparedStatements and CallableStatments. These two types are described in this section.

In addition to these statements, this section also describes the use of the ResultSetMetaData and DatabaseMetaData objects. You can use these last two objects to interrogate JDBC for information about a given set of query results, or for information about your database. The ability to get such information at run-time enables you to dynamically execute any SQL statement, even one that is unknown when you write your program.

CallableStatement

Callable statements are implemented by the CallableStatement object. A CallableStatement is a way to execute stored procedures in a JDBC-compatible database. The best reference for this is Sun's Javasoft website (http://java.sun.com/products/jdbc/), because it is a changing and evolving standard, and its application will depend greatly on your version of Java, and JDBC.

PreparedStatement

A PreparedStatement, in contrast to a CallableStatement, is used for SQL statements that are executed multiple times with different values.

For instance, you might want to insert several values, one after another. The advantage of the PreparedStatement is that it is pre-compiled, reducing the overhead of parsing SQL statements on every execution. Example 12-6 is an example of how a PreparedStatement might be used:

Example 12-6. A JDBC Prepared Statement

PreparedStatement ps = null;

try {
  ps = c.prepareStatement("INSERT INTO authors VALUES (?, ?, ?)");
  ps.setInt(1, 495);
  ps.setString(2, "Light-Williams");
  ps.setString(3, "Corwin");
} catch (SQLException se) {
  System.out.println("We got an exception while preparing a statement:" +
                     "Probably bad SQL.");
  se.printStackTrace();
  System.exit(1);
}

try {
  ps.executeUpdate();
} catch (SQLException se) {
  System.out.println("We got an exception while executing an update:" + 
                     "possibly bad SQL, or check the connection.");
  se.printStackTrace();
  System.exit(1);
}

You can see that Example 12-6 prepares a statement in a similar fashion to before, except it uses a question mark (?) character in place of each value that you want to supply. You use the appropriate PreparedStatement set method (e.g., setInt, setString) to set those values, depending on what data type the column is of.

This approach is useful, because it avoids manual conversion of Java types to SQL types. For instance, the implementor does not have to worry about quoting or escaping when going to a text type.

Notice that the first parameter passed to a set method indicates the order in which the value is to be inserted into the statement. A value of 1 corresponds to the first question mark, a value of 2 corresponds to the second, and so on.

The other strength of the PreparedStatement is that you can use it over and over again. If you set the values to be something else (in the same fashion as before, using the set methods) you can change the values being inserted.

Overall, the PreparedStatement mechanism is considerably more robust than the Statement class.

ResultSetMetaData

You can interrogate JDBC for detailed information about a query's result set using a ResultSetMetaData object. ResultSetMetaData is a class that is used to find information about the ResultSet returned from a executeQuery call. It contains information about the number of columns, the types of data they contain, the names of the columns, and so on.

Two of the most common methods in the ResultSetMetaData are getColumnName and getColumnTypeName. These retrieve the name of a column, and the name of its associated data type, respectively, each in the form of a String.

Warning

The getColumnType method is not the same as the getColumnTypeName. getColumnType returns an int corresponding to a data type's internal JDBC identification code, whereas getColumnTypeName returns the name as a String.

Example 12-7 is an example of using the ResultSetMetaData to get the name and data type of the first column in a ResultSet called rs. This code could logically followed the acquisition of the ResultSet named rs in Example 12-4.

Example 12-7. JDBC ResultSetMetaData

ResultSetMetaData rsmd = null;
try {
  rsmd = rs.getMetaData();
} catch (SQLException se) {
  System.out.println("We got an exception while getting the metadata:" +
                     "check the connection.");
  se.printStackTrace();
  System.exit(1);
}

String columnName = null,
       columnType = null;
try {
  columnName = rsmd.getColumnName(1);
  columnType = rsmd.getColumnTypeName(1);
} catch (SQLException se) {
  System.out.println("We got an exception while getting the column name:" +
                     "check the connection.");
  se.printStackTrace();
  System.exit(1);
}

System.out.print("The name of the first column is: '");
System.out.print(columnName);
System.out.println("'");
System.out.print("The data type of the first column is: ");
System.out.println(columnType);

There are many other such methods in the ResultSetMetaData class, all of which are well documented in the JDK API documentation.

DatabaseMetaData

Finally, DatabaseMetaData is a class that can be used to fetch information about the database you are using: what kind of catalogs it has in it, what tables, what brand of database, etc. Let's see what your user name is, and what URL you can reach the database at.

Example 12-8. JDBC Database MetaData

DatabaseMetaData dbmd = null;

try {
    dbmd = c.getMetaData();
} catch (SQLException se) {
    System.out.println("We got an exception while getting the metadata:" +
                       " check the connection.");
    se.printStackTrace();
    System.exit(1);
}

String username = null;
try {
    username = dbmd.getUserName();
} catch (SQLException se) {
  System.out.println("We got an exception while getting the username:" +
                     "check the connection.");
  se.printStackTrace();
  System.exit(1);
}

String url = null;
try {
  url = dbmd.getURL();
} catch (SQLException se) {
  System.out.println("We got an exception while getting the username:" +
                     "check the connection.");
  se.printStackTrace();
  System.exit(1);
}

System.out.println("You are connected to '" + url +
                   "' with user name '" + username + "'");

Once again, the best source for the most current information about DatabaseMetaData's many other methods is in the JDK API documentation.