|
Programming with the JDBC classes is, by design, not very different from programming with the usual Java platform classes: You build objects from the JDBC core classes, extending them by inheritance if need be. This section takes you through the details. NOTE
Database URLsWhen connecting to a database, you must specify the data source and you may need to specify additional parameters. For example, network protocol drivers may need a port, and ODBC drivers may need various attributes. As you might expect, JDBC uses a syntax similar to that of ordinary URLs to describe data sources. Here are examples of the syntax: jdbc:mckoi://localhost/ jdbc:postgresql:COREJAVA These JDBC URLs specify a local McKoi database and a PostgreSQL database named COREJAVA. The general syntax is
where a subprotocol selects the specific driver for connecting to the database. The format for the other stuff parameter depends on the subprotocol used. Look up your vendor's documentation for the specific format. Making the ConnectionFind out the names of the JDBC driver classes used by your vendor. Typical driver names are org.postgresql.Driver com.mckoi.JDBCDriver Next, find the library in which the driver is located, such as pg74jdbc3.jar or mkjdbc.jar. Use one of the following three mechanisms:
The DriverManager class selects database drivers and creates a new database connection. However, before the driver manager can activate a driver, the driver must be registered. The jdbc.drivers property contains a list of class names for the drivers that the driver manager will register at startup. Two methods can set that property. You can specify the property with a command-line argument, such as java -Djdbc.drivers=org.postgresql.Driver MyProg Or your application can set the system property with a call such as System.setProperty("jdbc.drivers", "org.postgresql.Driver"); You can also supply multiple drivers; separate them with colons, such as org.postgresql.Driver:com.mckoi.JDBCDriver NOTE
After registering drivers, you open a database connection with code that is similar to the following example: String url = "jdbc:postgresql:COREJAVA"; String username = "dbuser"; String password = "secret"; Connection conn = DriverManager.getConnection(url, username, password); The driver manager iterates through the available drivers currently registered with it to find a driver that can use the subprotocol specified in the database URL. For our example programs, we find it convenient to use a properties file to specify the URL, user name, and password in addition to the database driver. A typical properties file has the following contents: jdbc.drivers=org.postgresql.Driver jdbc.url=jdbc:postgresql:COREJAVA jdbc.username=dbuser jdbc.password=secret Here is the code for reading a properties file and opening the database connection. Properties props = new Properties(); FileInputStream in = new FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers"); if (drivers != null) System.setProperty("jdbc.drivers", drivers); String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); return DriverManager.getConnection(url, username, password); The getConnection method returns a Connection object. In the following sections, you will see how to use the Connection object to execute SQL statements. TIP
Example 4-1. TestDB.java1. import java.sql.*; 2. import java.io.*; 3. import java.util.*; 4. 5. /** 6. This program tests that the database and the JDBC 7. driver are correctly configured. 8. */ 9. class TestDB 10. { 11. public static void main (String args[]) 12. { 13. try 14. { 15. runTest(); 16. } 17. catch (SQLException ex) 18. { 19. while (ex != null) 20. { 21. ex.printStackTrace(); 22. ex = ex.getNextException(); 23. } 24. } 25. catch (IOException ex) 26. { 27. ex.printStackTrace(); 28. } 29. } 30. 31. /** 32. Runs a test by creating a table, adding a value, showing the table contents, and 33. removing the table. 34. */ 35. public static void runTest() 36. throws SQLException, IOException 37. { 38. Connection conn = getConnection(); 39. try 40. { 41. Statement stat = conn.createStatement(); 42. 43. stat.execute("CREATE TABLE Greetings (Message CHAR(20))"); 44. stat.execute("INSERT INTO Greetings VALUES ('Hello, World!')"); 45. 46. ResultSet result = stat.executeQuery("SELECT * FROM Greetings"); 47. result.next(); 48. System.out.println(result.getString(1)); 49. stat.execute("DROP TABLE Greetings"); 50. } 51. finally 52. { 53. conn.close(); 54. } 55. } 56. 57. /** 58. Gets a connection from the properties specified 59. in the file database.properties 60. @return the database connection 61. */ 62. public static Connection getConnection() 63. throws SQLException, IOException 64. { 65. Properties props = new Properties(); 66. FileInputStream in = new FileInputStream("database.properties"); 67. props.load(in); 68. in.close(); 69. 70. String drivers = props.getProperty("jdbc.drivers"); 71. if (drivers != null) 72. System.setProperty("jdbc.drivers", drivers); 73. String url = props.getProperty("jdbc.url"); 74. String username = props.getProperty("jdbc.username"); 75. String password = props.getProperty("jdbc.password"); 76. 77. return DriverManager.getConnection(url, username, password); 78. } 79. } Executing SQL CommandsTo execute a SQL command, you first create a Statement object. To create statement objects, use the Connection object that you obtained from the call to DriverManager.getConnection. Statement stat = conn.createStatement(); Next, place the statement that you want to execute into a string, for example, String command = "UPDATE Books" + " SET Price = Price - 5.00" + " WHERE Title NOT LIKE '%Introduction%'"; Then call the executeUpdate method of the Statement class: stat.executeUpdate(command); The executeUpdate method returns a count of the rows that were affected by the SQL command. For example, the call to executeUpdate in the preceding example returns the number of book records whose price was lowered by $5.00. The executeUpdate method can execute actions such as INSERT, UPDATE, and DELETE as well as data definition commands such as CREATE TABLE and DROP TABLE. However, you need to use the executeQuery method to execute SELECT queries. There is also a catch-all execute statement to execute arbitrary SQL statements. It's commonly used only for queries that a user supplies interactively. When you execute a query, you are interested in the result. The executeQuery object returns an object of type ResultSet that you use to walk through the result one row at a time. ResultSet rs = stat.executeQuery("SELECT * FROM Books") The basic loop for analyzing a result set looks like this:
CAUTION
NOTE
When inspecting an individual row, you will want to know the contents of the fields. A large number of accessor methods give you this information. String isbn = rs.getString(1); double price = rs.getDouble("Price"); There are accessors for various types, such as getString and getdouble. Each accessor has two forms, one that takes a numeric argument and one that takes a string argument. When you supply a numeric argument, you refer to the column with that number. For example, rs.getString(1) returns the value of the first column in the current row. CAUTION
When you supply a string argument, you refer to the column in the result set with that name. For example, rs.getDouble("Price") returns the value of the column with name Price. Using the numeric argument is a bit more efficient, but the string arguments make the code easier to read and maintain. Each get method makes reasonable type conversions when the type of the method doesn't match the type of the column. For example, the call rs.getString("Price") converts the floating-point value of the Price column to a string. NOTE
Advanced SQL TypesIn addition to numbers, strings, and dates, many databases can store large objects such as images or other data. In SQL, binary large objects are called BLOBs, and character large objects are called CLOBs. The getBlob and getClob methods return objects of type java.sql.Blob and java.sql.Clob. These classes have methods to fetch the bytes or characters in the large objects. A SQL ARRAY is a sequence of values. For example, in a Student table, you can have a Scores column that is an ARRAY OF INTEGER. The getArray method returns an object of type java.sql.Array (which is different from the java.lang.reflect.Array class that we discussed in Volume 1). The java.sql.Array interface has methods to fetch the array values. When you get a BLOB or an array from a database, the actual contents are fetched from the database only when you request individual values. This is a useful performance enhancement, since the data can be quite voluminous. Some databases can store user-defined structured types. JDBC supports a mechanism for automatically mapping structured SQL types to Java objects. We do not discuss BLOBs, arrays, and user-defined types any further. You can find more information on these topics in the book JDBC(TM) API Tutorial and Reference: Universal Data Access for the Java 2 Platform (2nd Edition) by Seth White, Maydene Fisher, Rick Cattell, Graham Hamilton, and Mark Hapner [Addison-Wesley 1999]. java.sql.DriverManager 1.1
java.sql.Connection 1.1
java.sql.Statement 1.1
java.sql.ResultSet 1.1
java.sql.SQLException 1.1
Managing Connections, Statements, and Result SetsEvery Connection object can create one or more Statement objects. You can use the same Statement object for multiple, unrelated commands and queries. However, a statement has at most one open result set. If you issue multiple queries whose results you analyze concurrently, then you need multiple Statement objects. Be forewarned, though, that at least one commonly used database (Microsoft SQL Server) has a JDBC driver that allows only one active statement at a time. Use the getMaxStatements method of the DatabaseMetaData class to find out the number of concurrently open statements that your JDBC driver supports. This sounds restrictive, but in practice, you should probably not fuss with multiple concurrent result sets. If the result sets are related, then you should be able to issue a combined query and analyze a single result. It is much more efficient to let the database combine queries than it is for a Java program to iterate through multiple result sets. When you are done using a ResultSet, Statement, or Connection, you should call the close method immediately. These objects use large data structures, and you don't want to wait for the garbage collector to deal with them. The close method of a Statement object automatically closes the associated result set if the statement has an open result set. Similarly, the close method of the Connection class closes all statements of the connection. If your connections are short-lived, you don't have to worry about closing statements and result sets. Just make absolutely sure that a connection object cannot possibly remain open, by placing the close statement in a finally block:
TIP
Populating a DatabaseWe now want to write our first, real, JDBC program. Of course, it would be nice if we could execute some of the fancy queries that we discussed earlier. Unfortunately, we have a problem: Right now, there are no data in the database. And you won't find a database file on the CD-ROM that you can simply copy onto your hard disk for the database program to read, because no database file format lets you interchange SQL relational databases from one vendor to another. SQL does not have anything to do with files. It is a language to issue queries and updates to a database. How the database executes these statements most efficiently and what file formats it uses toward that goal are entirely up to the implementation of the database. Database vendors try hard to come up with clever strategies for query optimization and data storage, and different vendors arrive at different mechanisms. Thus, although SQL statements are portable, the underlying data representation is not. To get around our problem, we provide a small set of data in a series of text files that contain the raw SQL instructions to create the tables and insert the values. We also give you a program that reads a file with SQL instructions, one instruction per line, and executes them. Specifically, the program reads data from a text file in a format such as CREATE TABLE Publisher (Publisher_Id CHAR(6), Name CHAR(30), URL CHAR(80)) INSERT INTO Publishers VALUES ('0201', 'Addison-Wesley', 'www.aw-bc.com') INSERT INTO Publishers VALUES ('0471', 'John Wiley & Sons', 'www.wiley.com') . . . At the end of this section, you can see the code for the program that reads the SQL statement file and executes the statements. Even if you are not interested in looking at the implementation, you must run this program if you want to execute the more interesting examples in the remainder of this chapter. Run the program as follows:
Before running the program, check that the file database.properties is set up properly for your environmentsee page 195. The following steps briefly describe the ExecSQL program:
Example 4-2 lists the code for the program. Example 4-2. ExecSQL.java[View full width] 1. import java.io.*; 2. import java.util.*; 3. import java.sql.*; 4. 5. /** 6. Executes all SQL statements in a file. 7. Call this program as 8. java -classpath driverPath:. ExecSQL commandFile 9. */ 10. class ExecSQL 11. { 12. public static void main (String args[]) 13. { 14. try 15. { 16. Scanner in; 17. if (args.length == 0) 18. in = new Scanner(System.in); 19. else 20. in = new Scanner(new File(args[0])); 21. 22. Connection conn = getConnection(); 23. try 24. { 25. Statement stat = conn.createStatement(); 26. 27. while (true) 28. { 29. if (args.length == 0) System.out.println("Enter command or EXIT to exit:"); 30. 31. if (!in.hasNextLine()) return; 32. 33. String line = in.nextLine(); 34. if (line.equalsIgnoreCase("EXIT")) return; 35. try 36. { 37. boolean hasResultSet = stat.execute(line); 38. if (hasResultSet) 39. showResultSet(stat); 40. } 41. catch (SQLException e) 42. { 43. while (e != null) 44. { 45. e.printStackTrace(); 46. e = e.getNextException(); 47. } 48. } 49. } 50. } 51. finally 52. { 53. conn.close(); 54. } 55. } 56. catch (SQLException e) 57. { 58. while (e != null) 59. { 60. e.printStackTrace(); 61. e = e.getNextException(); 62. } 63. } 64. catch (IOException e) 65. { 66. e.printStackTrace(); 67. } 68. } 69. 70. /** 71. Gets a connection from the properties specified 72. in the file database.properties 73. @return the database connection 74. */ 75. public static Connection getConnection() 76. throws SQLException, IOException 77. { 78. Properties props = new Properties(); 79. FileInputStream in = new FileInputStream("database.properties"); 80. props.load(in); 81. in.close(); 82. 83. String drivers = props.getProperty("jdbc.drivers"); 84. if (drivers != null) System.setProperty("jdbc.drivers", drivers); 85. 86. String url = props.getProperty("jdbc.url"); 87. String username = props.getProperty("jdbc.username"); 88. String password = props.getProperty("jdbc.password"); 89. 90. return DriverManager.getConnection(url, username, password); 91. } 92. 93. /** 94. Prints a result set. 95. @param stat the statement whose result set should be 96. printed 97. */ 98. public static void showResultSet(Statement stat) 99. throws SQLException 100. { 101. ResultSet result = stat.getResultSet(); 102. ResultSetMetaData metaData = result.getMetaData(); 103. int columnCount = metaData.getColumnCount(); 104. 105. for (int i = 1; i <= columnCount; i++) 106. { 107. if (i > 1) System.out.print(", "); 108. System.out.print(metaData.getColumnLabel(i)); 109. } 110. System.out.println(); 111. 112. while (result.next()) 113. { 114. for (int i = 1; i <= columnCount; i++) 115. { 116. if (i > 1) System.out.print(", "); 117. System.out.print(result.getString(i)); 118. } 119. System.out.println(); 120. } 121. result.close(); 122. } 123. } |
|