import javax.swing.JTextArea; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JScrollPane; import javax.swing.JTable; import java.
Trang 1array, you store a reference to it in dataRows After all the rows from the resultset have been stored, you callthe fireTableChanged()method that the ResultsModelclass inherits from the base class This methodnotifies all listeners for the JTableobject for this model that the model has changed, so the JTableobjectshould redraw itself from scratch The argument to the fireTableChanged()method is a reference to anobject of type TableModelEventthat you can use to record the parts of the model that have changed, andthis is passed to the listeners You pass a nullhere, as you want to invalidate the whole table.
The method to return the number of columns is now very easy to implement:
public int getColumnCount() {return columnNames.length;
}The column count is the number of elements in the columnNamesarray
Supplying the row count is just as easy:
public int getRowCount() { return dataRows == null ? 0 : dataRows.size();
}The number of rows corresponds to the size of the Vector<>object, dataRows You check to verify thatthe value of the dataRowsvector is not nullto ensure that the initialization of the InteractiveSQLGUI can take place even when the vector has not been initialized
The next method you need to define provides access to the data values You can implement this as lows:
fol-public String getValueAt(int row, int column) {return dataRows.elementAt(row)[column];
}The elementAt()method returns the element in the Vector<>object at the position specified by theargument This will be a reference to an array of type String[]so you just index this with the value ofthe columnparameter to select the element to be returned
The last method you must add to the class is getColumnName(), which will return the column namegiven a column index You can implement this as:
public String getColumnName(int column) {return columnNames[column] == null ? “No Name” : columnNames[column];
}You take the precaution here of dealing with a nullcolumn name by supplying a default column name
in this case
Trang 2The Application GUI
Figure 24-9 shows the user interface for the InteractiveSQLtool The text field at the top provides an entry area for typing in the SQL statement and will be implemented using a JTextFieldcomponent The results display provides a scrollable area for the results of the executed SQL command This will be implemented using a JScrollPanecomponent A status line, implemented as a JTextAreacomponent, provides the user with the number of rows returned from the query, or the text of any SQLException object generated by the query
Figure 24-9
Figure 24-9 also shows the menu items in the File menu and the tooltip prompt for the SQL input area The Clear query menu item will just clear the input area where you enter an SQL query
Try It Out Defining the GUI
You will derive the InteractiveSQLclass from the JFrameclass and make this the foundation for the application Its constructor will be responsible for loading the JDBC driver class, creating a connection to the database, and creating the user interface The code is as follows:
import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
Trang 3import javax.swing.JTextArea;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
public class InteractiveSQL extends JFrame { public static void main(String[] args) { // Create the application object InteractiveSQL theApp = new InteractiveSQL(“sun.jdbc.odbc.JdbcOdbcDriver”,
“jdbc:odbc:technical_library”,
“guest”,
“guest”);
} public InteractiveSQL(String driver, String url,
String user , String password) { super(“InteractiveSQL”); // Call base constructor setBounds(0, 0, 400, 300); // Set window bounds setDefaultCloseOperation(DISPOSE_ON_CLOSE); // Close window operation addWindowListener(new WindowAdapter() { // Listener for window close
// Handler for window closing event public void windowClosing(WindowEvent e) { dispose(); // Release the window resources System.exit(0); // End the application
} } );
// Add the input for SQL statements at the top command.setToolTipText(“Key SQL commmand and press Enter”);
getContentPane().add(command, BorderLayout.NORTH);
// Add the status reporting area at the bottom status.setLineWrap(true);
status.setWrapStyleWord(true);
getContentPane().add(status, BorderLayout.SOUTH);
// Create the menubar from the menu items JMenu fileMenu = new JMenu(“File”); // Create File menu fileMenu.setMnemonic(‘F’); // Create shortcut fileMenu.add(clearQueryItem); // Add clear query item fileMenu.add(exitItem); // Add exit item menuBar.add(fileMenu); // Add menu to the menubar setJMenuBar(menuBar); // Add menubar to the window // Establish a database connection and set up the table
try { Class.forName(driver); // Load the driver connection = DriverManager.getConnection(url, user, password);
statement = connection.createStatement();
Trang 4model = new ResultsModel(); // Create a table modelJTable table = new JTable(model); // Create a table from the modeltable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); // Use scrollbarsresultsPane = new JScrollPane(table); // Create scrollpane for tablegetContentPane().add(resultsPane, BorderLayout.CENTER);
} catch(ClassNotFoundException cnfe) {System.err.println(cnfe); // Driver not found} catch(SQLException sqle) {
System.err.println(sqle); // error connection to database}
pack();
setVisible(true);
}
JTextField command = new JTextField(); // Input area for SQL
JTextArea status = new JTextArea(3,1); // Output area for status and errorsJScrollPane resultsPane;
JMenuBar menuBar = new JMenuBar(); // The menu bar
JMenuItem clearQueryItem = new JMenuItem(“Clear query”); // Clear SQL item
JMenuItem exitItem = new JMenuItem(“Exit”); // Exit item
Connection connection; // Connection to the database
Statement statement; // Statement object for queriesResultsModel model; // Table model for resultset
}
You can try running the application as it is, and you should see the basic application interface displayed
in the window with a working close operation
How It Works
The constructor is passed the arguments required to load the appropriate driver and create a
Connectionto a database The first executable statement in this constructor calls the constructor for theJFrameclass, passing a default window title to it The constructor then creates and arranges the userinterface components Most of this should be familiar to you, but let’s pick out a few things that are new,
or are worthy of a second look
You can see how you add a tooltip for the JTextFieldcomponent command— the input area for an SQLstatement Don’t forget that you can add a tooltip for any Swing component in the same way
You define the JTextAreaobject, status, so that it can display three lines of text The first argument tothe JTextAreaconstructor is the number of lines of text, and the second argument is the number ofcolumns Some of the error messages can be quite long, so you call both the setLineWrap()method tomake lines wrap automatically, and the setWrapStyleWord()method to wrap a line at the end of aword — that is, on whitespace — rather than in the middle of a word In both cases the trueargumentswitches the facility on
You create the JTableobject using the default ResultsModelobject, which will contain no data tially Since the number of columns in a resultset will vary depending on the SQL query that is executed,you wrap the JTableobject in a JScrollPaneobject to provide automatic scrolling as necessary Thescrollbars will appear whenever the size of the JTableobject is larger than the size of the scroll pane By
Trang 5ini-default, a JTableobject will resize the width of its columns to fit within the width of the JTableponent To inhibit this and allow the scroll pane scrollbars to be used, you call the
com-setAutoResizeMode()method with the argument as JTable.AUTO_RESIZE_OFF.This not only inhibits the default resizing action when the table is displayed, but also allows you tochange the size of a column when the table is displayed without affecting the size of the other columns.You change the size of a column by dragging the side of the column name using the mouse There areother values defined in the JTableclass that you can pass to the setAutoResizeMode()method todetermine how resizing is handled:
AUTO_RESIZE_ALL_COLUMNS Adjusts the sizes of all columns to take up the
change in width of the column being resized Thismaintains the overall width of the table
AUTO_RESIZE_NEXT_COLUMN Adjusts the size of the next column to provide for
the change in the column being altered in order tomaintain the total width of the table
AUTO_RESIZE_LAST_COLUMN Adjusts the size of the last column to provide for
the change in the column being altered in order tomaintain the total width of the table
AUTO_RESIZE_SUBSEQUENT_COLUMNS Adjusts the size of the columns to the right to
pro-vide for the change in the column being altered inorder to maintain the total width of the table
Handling Events
Of course, the program doesn’t do anything because there’s no code in the program to respond to SQLcommands that you enter in the text field or to menu item selection The first step is to make theInteractiveSQLclass implement the ActionListenerinterface Change the first line of the class definition to:
public class InteractiveSQL extends JFrame implements ActionListener {You can define the actionPerformed()method in the InteractiveSQLclass like this:
public void actionPerformed(ActionEvent e) {Object source = e.getSource();
if(source == command) { // Enter key for text field inputexecuteSQL();
} else if(source == clearQueryItem) { // Clear query menu itemcommand.setText(“”); // Clear SQL entry} else if(source == exitItem) { // Exit menu itemdispose(); // Release the window resourcesSystem.exit(0); // End the application
} }
Trang 6This method is handling events from the text field and the menu items, so the action to be carried outdepends on the object that originated the event You determine which object originated the event bycomparing the reference returned by the getSource()method for the event object with the three fields
in the InteractiveSQLobject If it’s the text field, you call the executeSQL()method that you’ll addnext; this will execute the SQL command that was entered If it’s the clearQueryItemmenu item, youcall the setText()method for the JTextFieldobject to reset the contents to an empty string If it’s theexitItemmenu item, you just exit the program after releasing the window resources
You can define the method that will enter the SQL command that was entered like this:
public void executeSQL() {
String query = command.getText(); // Get the SQL statementif(query == null ||query.length() == 0) { // If there’s nothing we are donereturn;
}try {model.setResultSet(statement.executeQuery(query));
status.setText(“Resultset has “ + model.getRowCount() + “ rows.”);
} catch (SQLException sqle) {status.setText(sqle.getMessage()); // Display error message}
}
Calling the getText()method for the JTextFieldobject returns a reference to the string that wasentered If it’s nullor an empty string, there’s nothing to be done, so the method returns immediately Ifit’s not null, you pass the query string to the executeQuery()method for the Statementobject Thiswill return a reference to a ResultSetobject containing the results of the query, and you pass this to thesetResultSet()method for the ResultsModelobject This sets the new resultset in the model andcauses the JTableobject to redisplay itself with the new data from the table model Finally, you displaythe number of rows returned by the query in the text area below the table
Of course, you still have to identify the InteractiveSQLobject as the listener for the text field and thetwo menu items, so add the following code to the constructor after the code that sets up the menu:
menuBar.add(fileMenu); // Add menu to the menubarsetJMenuBar(menuBar); // Add menubar to the window// Add listeners for text field and menu items
Trang 7Handling Command-Line Arguments
All you need to do is to alter the main()method to accept up to four command-line arguments; thesewill be the values for the user name, password, database URL, and JDBC driver
Try It Out Using Command-Line ParametersYou need to modify the code in main()to:
public static void main(String[] args) {// Set default values for the command line argsString user = “guest”;
String password = “guest”;
String url = “jdbc:odbc:technical_library”;
String driver = “sun.jdbc.odbc.JdbcOdbcDriver”;
// Up to 4 arguments in the sequence database url,driver url, user ID, passwordswitch(args.length) {
case 4: // Start here for four argumentspassword = args[3];
// Fall through to the next casecase 3: // Start here for three argumentsuser = args[2];
// Fall through to the next casecase 2: // Start here for two argumentsdriver = args[1];
// Fall through to the next casecase 1: // Start here for one argumenturl = args[0];
}InteractiveSQL theApp = new InteractiveSQL(driver, url, user, password);
}
How It Works
Now the program enables you to optionally specify the JDBC URL, the JDBC driver, the user name, andthe password on the command line If you don’t supply any command-line arguments, the programworks as before, accessing the technical_librarydatabase
The mechanism that handles the optional parameters is pretty simple The switchstatement tests thenumber of parameters that were specified on the command line If one parameter was passed, it is inter-preted as the JDBC URL If two parameters were passed, the second parameter is assumed to be thedriver URL, and so on There are no breakstatements, so control always drops through from the start-ing case to include each of the following cases
Summar y
In this chapter you’ve been introduced to JDBC programming and seen it in action The important pointscovered in this chapter include the following:
Trang 8❑ The fundamental classes in JDBC are as follows:
❑ DriverManagermanages the loading of JDBC drivers and connections to client cations
appli-❑ Connectionprovides a connection to a specific data source
❑ Statementprovides a context for executing SQL statements
❑ ResultSetprovides a means for accessing data returned from an executedStatement
❑ The essential JDBC program has the following basic sequence when writing:
❑ Import the necessary classes
❑ Load the JDBC driver
❑ Identify the data source
❑ Allocate a Connectionobject
❑ Allocate a Statementobject
❑ Execute a query using the Statementobject
❑ Retrieve data from the returned ResultSetobject
❑ Close the ResultSet
❑ Close the Statementobject
❑ Close the Connectionobject
❑ The JTablecomponent provides an easy and convenient way to display the results of databasequeries
❑ A table model can provide the data to be displayed by a JTablecomponent A table model is anobject of a class that implements the TableModelinterface
3. Modify the InteractiveSQLprogram to allow the user to specify which database to use
4. Modify the InteractiveSQLprogram to provide separate input areas for each part of aSELECTstatement — one for the table columns, one for the table, one for a possible WHEREclause, and so on
Trang 9The JDBC in Action
In this chapter I’ll expand on the topics that I introduced in the previous chapter, and go into moredetail on the Java Database Connectivity (JDBC) application program interface (API) In this chap-ter you’re going to learn more about:
❑ How you map relational data onto Java objects
❑ The mapping between SQL and Java data types
❑ How you limit the data created in a resultset
❑ How you constrain the time spent executing a query
❑ How you use a PreparedStatementobject to create a parameterized SQL statement
❑ How you can execute database update and delete operations in your Java programs
❑ How you can get more information from SQLExceptionobjects
❑ What an SQLWarningobject is and what you can do with it
Data Types and JDBC
In all of the examples so far, all of the data extracted from a resultset was retrieved as a String.You’ll certainly need to get other types of data, and as you saw in the previous chapter, theResultSetprovides a number of methods for retrieving different data types To use these effec-tively, you need to look at the SQL data types and understand how they map to the Java datatypes in your program
Mapping between Java and SQL Data Types
The SQL-92 standard defines a set of data types that don’t map one-for-one with those in Java Asyou write applications that move data from SQL to Java and back, you’ll have to take account ofhow JDBC performs that mapping That is, you need to know the Java data type you need to rep-resent a given SQL data type, and vice versa
Trang 10The Typesclass in the java.sqlpackage defines constants of type intthat represent each of the ported SQL types The name given to the data member storing each constant is the same as that of thecorresponding SQL type For example, when you retrieve the SQL type of a table column by calling thegetColumnType()method for a ResultSetMetaDataobject, the SQL type is returned as one of theconstants defined in the Typesclass.
sup-When you’re retrieving data from a JDBC data source, the ResultSetimplementation will map the SQLdata onto Java data types The following table shows the SQL-to-Java mappings:
Conversely, when you are relating Java-to-SQL data types, the following mappings apply:
Trang 11Java Data Type SQL Data Type
Mapping Relational Data onto Java Objects
In the previous chapter, you saw how you could get the basic attribute data from a JDBC ResultSetobject Since Java is object-oriented, in many cases you won’t want to deal with individual data itemssuch as the authors’ names and IDs — you’ll want to work with Authorobjects that represent theauthors That’s what I’ll focus on now, and in the process, you’ll get some more experience with theStatementand ResultSetinterfaces
The way that information is handled at the object level is usually different from the way that data isstored in a relational database In the world of objects, the underlying principle is to make those objectsexhibit the same characteristics (information and behavior) as their real-world counterparts — in otherwords, objects function at the level of the conceptual model Relational databases, on the other hand,
Note that some databases implement INTEGERdata types as NUMERIC When ing INTEGERelements through the JDBC, it is important to associate the JDBC data type with the internal data type that is actually stored in the database.
Trang 12access-work at the data model level As you saw in the previous chapter, relational databases store informationusing normalized forms, where conceptual objects like invoices and customers can be decomposed into
a number of tables So how do you deal with the problem of mapping objects to relational data models?
Sometimes there is a straightforward relationship between the columns in a table and the member ables in an object In that case, the mapping task consists simply of matching the data types of thedatabase with those of Java Figure 25-1 shows this simple application-level SQL-to-object mapping
vari-Figure 25-1
Try It Out A Simple Mapping from SQL Rows to Java Objects
The authorstable in the sample database is a good example of a simple mapping To recap, this tablehas the following definition:
Application
JDBC
Databaseauthors table
Authorobject
Call JDBC method to specifySQL SELECT statement
The application extracts datadescribing an author from thedatabase as a ResultSet object
The application creates theAuthor object from datacontained within the ResultSet
ResultSetobject
TabledataSQL
SELECT
Trang 13Column Data Type Description
Let’s define a Java class to encapsulate an author Take a look back at the table that shows you how tomap SQL to Java data types Based on those mappings, you can define the member variables for anAuthorclass, and add a constructor, member access methods, and a toString()method:
public class Author {public Author(int authid, String lastname, String firstname,
String address[], String city, String state,String postcode, String country,
String phone, String fax, String email){
}public String getLastName() {return lastname;
}public String getFirstName() {return firstname;
}public String[] getAddress() {return address;
}public String getCity() {return city;
}
Trang 14public String getState() {
public String toString() {
return new String(“author ID: “ + Integer.toString(authid) +
“\nname : “ + lastname + “,” + firstname +
Trang 15try {SQLtoJavaExample = new TrySimpleMapping();
SQLtoJavaExample.listAuthors();
} catch(SQLException sqle) {System.err.println(sqle);
} catch(ClassNotFoundException cnfe) {System.err.println(cnfe);
}}public TrySimpleMapping() throws SQLException, ClassNotFoundException {Class.forName (driverName);
connection = DriverManager.getConnection(sourceURL, user, password);
}public void listAuthors() throws SQLException {Author author = null;
String query = “SELECT authid, lastname, firstname, address1,”+
“address2, city, state_prov, postcode, country,”+
“phone, fax, email FROM authors”;
Statement statement = connection.createStatement();
ResultSet authors = statement.executeQuery(query);
while(authors.next()) {int id = authors.getInt(1);
String lastname = authors.getString(2);
String firstname = authors.getString(3);
String[] address = { authors.getString(4), authors.getString(5)};
String city = authors.getString(6);
String state = authors.getString(7);
String postcode = authors.getString(8);
String country = authors.getString(9);
String phone = authors.getString(10);
String fax = authors.getString(11);
String email = authors.getString(12);
author = new Author(id, lastname, firstname,
address, city, state, postcode,country, phone, fax, email);
System.out.println(“\n” + author);
}authors.close();
connection.close();
Trang 16Connection connection;
String driverName = “sun.jdbc.odbc.JdbcOdbcDriver”;
String sourceURL = “jdbc:odbc:technical_library”;
String user = “guest”;
String password = “guest”;
con-as the column names in the SQL query To display the data for each Authorobject, you simply callSystem.out.println()and pass the Authorobject reference to it This will automatically invoke thetoString()method for the object Notice that in the output, the literal nullappears where there arenullvalues in the database
This example uses the JDBC-ODBC Bridge driver with a data source that does not
require a user name or password If you need a user name and password to access that
data source, simply modify the code in the TrySimpleMappingconstructor to use the
appropriate driver, URL, and getConnection()method of the DriverManager.
Trang 17A Better Mapping Strategy
As you saw, the simple strategy described in the previous section does in fact transfer the data betweenthe relational database and the Java objects successfully (and this approach can be used in reverse to getdata back to the database, as you’ll see shortly) It does, however, leave quite a lot to be desired becausethe movement of data between the database and the Java object is left completely to the application code
A better, more object-oriented strategy would be to make the Authorclass handle its own data tion from a ResultSetobject To do this, you could add to the Authorclass a staticfactory method (amethod that manufactures Authorobjects) that will synthesize Authorobjects from data in the
extrac-database The code calling the factory method must still do the work of creating the ConnectionandStatementobjects and use the Statementobject to execute the query that retrieves the data It will alsoneed to ensure that the ResultSetcontains the columns required for populating the Authorobject.You need to establish an implied “contract” between this factory method and any code that calls it:
❑ The current row of the ResultSetobject that is passed to the factory method must be tioned at a valid row
posi-❑ The ResultSetmust contain all the columns from the authorstable
You can implement the factory method in the Authorclass as:
public static Author fromResults(ResultSet authors) throws SQLException {String[]address = {
authors.getString(“address1”),authors.getString(“address2”)};
return new Author(
authors.getInt(“authid”),authors.getString(“lastname”),authors.getString(“firstname”),address,
authors.getString(“city”),authors.getString(“state_prov”),authors.getString(“postcode”),authors.getString(“country”),authors.getString(“phone”),authors.getString(“fax”),authors.getString(“email”));
}Here you access the columns by name, so there is no dependency on the order in which they areretrieved in the query This gives a little added flexibility to the application — to use the wildcard nota-tion, for example The only requirement is that all the columns should be present in the ResultSetobject If any are not, an exception of type SQLExceptionwill be thrown, and this will need to be caught
by the calling method Of course, the Author.javafile must now have importstatements added forthe ResultSetand SQLExceptionnames from the java.sqlpackage
You can see this in action with another example
Trang 18Try It Out Encapsulated Mapping of SQL Rows to Java Objects
You just need to create the application class This class is nearly identical to TrySimpleMapping, exceptthat there’s less code in the listAuthors()method:
public class TryEncapsulatedMapping {
public static void main (String[] args) {
TryEncapsulatedMapping SQLtoJavaExample;
try {SQLtoJavaExample = new TryEncapsulatedMapping();
SQLtoJavaExample.listAuthors();
} catch(SQLException sqle) {System.err.println(sqle);
} catch(ClassNotFoundException cnfe) {System.err.println(cnfe);
}}
public TryEncapsulatedMapping() throws SQLException,
ClassNotFoundException {Class.forName (driverName);
connection = DriverManager.getConnection(sourceURL, user, password);
}
public void listAuthors() throws SQLException {
Author author;
String query = “SELECT authid, lastname, firstname, address1,”+
“address2, city, state_prov, postcode, country,”+
“phone, fax, email FROM authors”;
Statement statement = connection.createStatement();
ResultSet authors = statement.executeQuery(query);
while(authors.next())System.out.println(“\n” + Author.fromResults(authors));
authors.close();
connection.close();
}
Connection connection;
String driverName = “sun.jdbc.odbc.JdbcOdbcDriver”;
String sourceURL = “jdbc:odbc:technical_library”;
String user = “guest”;
String password = “guest”;
}
When you run the example, you should get results that are exactly the same as those from the previousexample
Trang 19How It Works
All you’ve really done in this example is push the work of extracting Java types from the ResultSettothe class that is using the data Instead of reading from the ResultSetand instantiating a new Authorobject for each row in the listAuthors()method, you just call the static fromResults()method ofthe Authorclass, which will create a new Authorobject from the data in the current row of theResultSet
This approach is better than the previous example because the class itself is responsible for ensuring thatthe correct mapping is performed between the database and the Java object That way, applications don’thave to duplicate that logic and don’t have the opportunity to attempt bad mappings (such as convert-ing an SQLREALtype to an int) The mapping is also independent of the sequence of columns in theresultset Encapsulation of the mapping from the database data to the class object is important for ensur-ing that classes can be reused easily within and between applications; therefore, although it’s a littlemore work than the simple mapping method, it’s well worth it
The Statement and PreparedStatement Interfaces
In this section you’re going to look in more detail at the Statementand PreparedStatementinterfaces.You’ll start with the Statementinterface, where you’ll learn about the methods that allow you to con-strain the query and how to handle data definition and data manipulation Next, you’ll look at thePreparedStatement, explore the differences between static and dynamic statements, and work withthe PreparedStatementinterface
The Statement Interface
You were introduced to the Statementinterface in the previous chapter The Statementinterfacedefines a set of methods that are implemented by an object returned to your program when you call thecreateStatement()method for the Connectionobject:
try {Statement queryStatement = connection.createStatement();
//
} catch(SQLException sqle) {System.err.println(sqle);
}Like pretty much every other method defined by JDBC, this code must be within a tryblock andinclude a catchblock for SQLException
Once the Statementinterface has been created, defining the query is as simple as building a Stringcontaining a valid SQL statement and passing that statement as the argument to the executeQuery()method of the Statementobject The SQL query can be a literal, or it can be a Stringvalue that youbuild at run time, as was the case in the InteractiveSQLapplication in the previous chapter, where theapplication obtains the SQL string from the text field just before the statement is executed
Trang 20Constraining the Resultset
In general, you won’t normally know how much data will be returned from executing a query In thetechnical_libraryexample, there isn’t any possibility of getting into difficulties because of the vol-ume of data, but with production databases you may need some controls Getting a million rows backfrom a SELECToperation could be an embarrassment, not only because a substantial amount of time will
be involved, but also because a large amount of memory will be needed to store the data The
Statementinterface allows you to set constraints on the consequences of executing a query You canlimit the number of rows in the resultset that are returned, as well as specify the maximum field size.You can also limit the amount of time that is allowed for a query to execute
Maximum Number of Rows
The JDBC driver may impose a limitation on how many rows may be returned by a query, and you maywish to impose your limit on how many rows are returned in a resultset The Statementinterfacedefines the getMaxRows()and setMaxRows()methods that allow you to query and set the maximumnumber of rows returned in the ResultSetobject An argument value 0is defined as no limit
A particular JDBC driver may default to a practical limit on the number of rows in a resultset or mayeven have implementation restrictions that limit the number of rows that are returned To determine therow limit in effect, you can call the getMaxRows()method for your Statementobject:
Statement statement = connection.createStatement();
int maxRows = statement.getMaxRows();
When you wish to limit the number of rows returned from a query in an application, to prevent anextremely lengthy query process, for example, you call setMaxRows()to limit the number of rowsreturned:
SQLStatement.setMaxRows(30);
Maximum Field Size
The Statementinterface also enables you to query and set the maximum field size that applies to allcolumn values returned in a ResultSet Querying this value will tell you if the JDBC driver imposes apractical or absolute limit on the size of the columns returned The value 0is defined as no limit
To determine the maximum field size for statement results, simply call the getMaxFieldSize()method
of the Statement:
Statement statement = connection.createStatement();
int maxFieldSize = statement.getMaxFieldSize();
It’s important to realize that when the maximum row count is set to a non-zero value
(zero being unlimited), you won’t get any indication when the data that would have
been returned has been truncated If the total number of rows exceeds the maximum
value, the maximum number of rows are returned in the resultset, and any
remain-ing rows that meet the query criteria will be silently left behind.
Trang 21The value returned is the maximum number of bytes permitted for any field returned in a resultset Likethe maximum row method pair, there is a corresponding setMaxFieldSize()method to set the maxi-mum field size:
SQLStatement.setMaxFieldSize(4096);
Note that the setMaxFieldSize()method applies only to columns with the following SQL data types:
Any bytes in a field in excess of the maximum you have set will be silently discarded
Query Time-Out
Depending on your JDBC driver and the database to which it is attached, there may be an executiontimeout period after which a query will fail and the executeQuery()method will throw an exception.You can check the value for the time-out period with the getQueryTimeout()method for a Statementobject; you can also set the time-out period (for example, if you want a query to fail after a fixed timeperiod) using the setQueryTimeout()method The time-out period is defined in seconds, and a time-out value of 0indicates that there is no limit on the time that a query can take
Here’s a simple program that will test the default query constraints for your JDBC driver You can tute an appropriate URL and driver name if you have other JDBC drivers available
substi-Try It Out Query ConstraintsSince this program is tiny, you’ll incorporate everything into the main()method:
try {String url = “jdbc:odbc:technical_library”;
String driver = “sun.jdbc.odbc.JdbcOdbcDriver”;
String username = “guest”;
String password = “guest”;
Trang 22// Put each method call in a separate try block to execute them alltry {
System.out.print(“\nMaximum rows :”);
int maxRows = statement.getMaxRows();
System.out.print(maxRows == 0 ? “ No limit” : “ “ + maxRows);
} catch (SQLException sqle) {System.err.print(sqle);
}try {System.out.print(“\nMax field size :”);
int maxFieldSize = statement.getMaxFieldSize();
System.out.print(maxFieldSize == 0 ? “ No limit” : “ “ + maxFieldSize);} catch (SQLException sqle) {
System.err.print(sqle);
}try {System.out.print(“\nTimeout :” );
int queryTimeout = statement.getQueryTimeout();
System.out.print(queryTimeout == 0 ? “ No limit” : “ “ + queryTimeout);} catch (SQLException sqle) {
System.err.print(sqle);
}}
}
Running this with Access, I got the following output:
Driver : sun.jdbc.odbc.JdbcOdbcDriver
Maximum rows : No limit
Max field size :java.sql.SQLException: [Microsoft][ODBC Microsoft Access
Driver]Optional feature not implemented
Timeout :java.sql.SQLException: [Microsoft][ODBC Microsoft Access
Driver]Optional feature not implemented
You can see that the underlying ODBC driver doesn’t support time-out or field size constraints — moresophisticated drivers are likely to support these methods
How It Works
This code is pretty simple It creates a Connectionusing a URL defining an Access database calledtechnical_library Once the connection is established, a Statementobject is created, and using thatthe values for the query time-out period, the maximum column size, and the maximum number of rowscan be executed All three methods providing this information will throw an exception of type
SQLExceptionif the information is not available — as is the case with the Microsoft Access driver Tomake sure that you do call all three methods, even when an exception is thrown, each method call is in aseparate tryblock
Executing DDL and DML
As you know, the executeQuery()method is used to execute an SQL query statement — a statementthat is expected to return some results in a resultset As I indicated in the previous chapter, there are other
Trang 23types of SQL statements that do not return results These statements fall into two primary categories:Data Definition Language (DDL) statements and Data Manipulation Language (DML) statements.
❑ DDL statements are those that change the structure of a database, such as CREATE TABLEandDROP TABLE
❑ DML statements are those that change the contents of the database, such as INSERT, UPDATE,and DELETEstatements
So far, all of the examples you have seen, including the InteractiveSQLapplication, have used theexecuteQuery()method If you tried to execute an SQL statement that didn’t produce a resultset withthe InteractiveSQLprogram, such as any DDL or DML, you would see an exception message reported
on the status line This is shown in Figure 25-2
Figure 25-2
The exception containing the message “No ResultSet was produced” is thrown because theexecuteQuery()method expects only an SQL statement that generates results (note that even though an exception was thrown in this case, the SQL statement was still executed)
The Statementinterface provides the executeUpdate()method to execute statements that change the contents of the database rather than return results Like executeQuery(), the executeUpdate()method accepts a single argument of type Stringspecifying the SQL statement that is to be executed.You can use the executeUpdate()method to execute UPDATE, INSERT, or DELETESQL statements You
Trang 24can also use it to execute DDL statements The method returns a value of type intthat indicates thenumber of rows affected by the operation when the database contents are changed, or 0for statementsthat do not alter the database.
The code fragment below illustrates use of the executeUpdate()method to add a row to the authorstable:
I split the SQL command into two strings simply because it will not fit on a single line in the book
Using the executeUpdate()method, it is pretty easy to write a utility to create and populate a table.The next example does exactly that In fact, this example is similar to the build_tablesutility includedwith the book’s source code, except that the latter reads an SQL statement from an external file
Try It Out Executing DDL and DML
Again, this is a small example, so the code will all be contained in the main()method The URL and thedriver are identified by the urland driverstrings:
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
public class BuildTables {
public static void main(String[] args) {
try {String username = “guest”;
String password = “guest”;
String url = “jdbc:odbc:technical_library”;
String driver = “sun.jdbc.odbc.JdbcOdbcDriver”;
String[] SQLStatements = {
“CREATE TABLE online_resources (pub_id int, name char(48), url char(80))”,
“INSERT INTO online_resources VALUES(1, ‘Wrox Home Page’,” +
Class.forName(driver);
Trang 25Connection connection = DriverManager.getConnection(url, username, password);Statement statement = connection.createStatement();
for (String SQLStatement : SQLStatements) {statement.executeUpdate(SQLStatement);
System.out.println(SQLStatement);
}} catch (ClassNotFoundException cnfe) {System.err.println(cnfe);
} catch (SQLException sqle) {System.err.println(sqle);
}}}The SQLStatements Stringarray contains the DDL and DML that will be executed by this program.The forloop simply iterates through each statement in the array and executes it using the
executeUpdate()method of the Statement.You can check the results by running the InteractiveSQLapplication on the table you created andobserving that the rows were inserted To do this, start that application and execute the SQL statement:
SELECT * FROM online_resourcesAfter you’re satisfied with your results, feel free to delete the table Using a new instance of theInteractiveSQLapplication, execute the statement:
DROP TABLE online_resourcesInteractiveSQLwill complain that no ResultSetis produced, but will dispose of the table nevertheless
How It Works
The BuildTablesprogram is very simple The Stringarray SQLStatementscontains all of the SQLstatements that you want to execute with executeUpdate()— one statement in each element of thearray Note that you concatenate two Stringliterals for three of the array element values just to makethe presentation clearer on the page here The forloop iterates through that array and executes andprints each statement in turn As usual, the code is inside a tryblock to catch any exceptions that might
be thrown
The only differences between this example and the other examples you’ve seen are that the SQL ments are executed with the executeUpdate()method instead of the executeQuery()method, andinstead of a ResultSetbeing returned, the method returns the number of rows affected by the operation
state-The PreparedStatement Interface
Earlier in this chapter, you saw that you can build SQL strings on the fly and execute them with theexecuteQuery()method of a Statement That is one way to introduce parameters into an SQL state-ment, but it is not the only way, nor is it necessarily the most convenient The PreparedStatementinterface provides an alternative mechanism that enables you to define an SQL statement with
placeholdersfor arguments Placeholders are tokens that appear in the SQL statement that are replaced
Trang 26by actual values before the SQL statement is executed This is usually much easier than building an SQLstatement with specific values by concatenating strings.
Like a Statementobject, you create a PreparedStatementobject by calling a method for a
Connectionobject Instead of calling the createStatement()method of the Connectionobject, youcall the prepareStatement()method when you want to create a PreparedStatementobject Whileyou can use a Statementobject to execute any number of different SQL statements, a
PreparedStatementobject executes only one predefined SQL statement that has placeholders for thevariable parts of the statement You specify the SQL statement that a PreparedStatementobject repre-sents by an argument of type Stringto the prepareStatement()method The argument specifies theSQL statement with each placeholder for a value represented by a ?character For example:
String newLastName = “UPDATE authors SET lastname = ? WHERE authid = ?”;
PreparedStatement updateLastName = connection.prepareStatement(newLastName);
The first statement defines a Stringobject specifying an UPDATEstatement with placeholders for thelast name and the author ID values This string is passed as the argument to the prepareStatement()method call that creates the PreparedStatementobject, updateLastName This will allow any valuefor lastnameto be set for any authid
Setting Query Parameters
You must set values for all the placeholders that appear in the statement encapsulated by the
updateLastNamereference before the statement can be executed You supply the value for each holder by calling one of the setXXX()methods of the PreparedStatementinterface:
These methods accept, minimally, a position argument that identifies which placeholder you are ring to and a value argument that is the value to be substituted for the placeholder Placeholders areindexed in sequence from left to right starting at 1, so you reference the leftmost placeholder with a posi-tion index of 1, and any placeholders that follow with index values of 2, 3, and so on
refer-The method that you call for a particular placeholder for an input value depends of the SQL type of thedestination column You must select the method that corresponds to the field type, so you would usesetInt()for type INTEGER, for example After calling the appropriate setXXX()method for eachplaceholder in the PreparedStatementobject, you can execute the SQL statement that the
PreparedStatementobject represents either by calling its executeQuery()method if the statementgenerates a resultset or by calling its executeUpdate()method to update or otherwise alter thedatabase Neither method requires an argument since the PreparedStatementobject already has itsSQL statement defined
Trang 27Once all the placeholders have values set and you have executed the statement, you can update any orall of the placeholders (or even none) before re-executing the statement The following code fragmentshows the PreparedStatementplaceholder value replacement in action:
// Create a PreparedStatement to update the lastname field for an authorString changeLastName = “UPDATE authors SET lastname = ? WHERE authid = ?”;
PreparedStatement updateLastName = connection.prepareStatement(changeLastName);updateLastName.setString(1,”Martin”); // Set lastname placeholder valueupdateLastName.setInt(2,4); // Set author ID placeholder valueint rowsUpdated = updateLastName.executeUpdate(); // execute the update
Note that placeholders for string arguments are not quoted — they are just a ?character, and thePreparedStatementautomatically sets up the empty placeholder Also, it’s perfectly okay to setparameters in whatever order you choose — you don’t have to set the first placeholder first, the secondone next, and so forth, just so long as they are all set before the statement is executed
Let’s try the code fragment above in an example that will change the last name of the author whoseauthidis 4
Try It Out Using a PreparedStatement ObjectTry out the following code — only the bits that are of particular interest are shaded here:
String url = “jdbc:odbc:technical_library”;
String driver = “sun.jdbc.odbc.JdbcOdbcDriver”;
String user = “guest”;
String password = “guest”;
Class.forName(driver);
Connection connection = DriverManager.getConnection(url);
String changeLastName = “UPDATE authors SET lastname = ? WHERE authid = ?”;PreparedStatement updateLastName =
connection.prepareStatement(changeLastName);updateLastName.setString(1,” Martin”); // Set lastname placeholder valueupdateLastName.setInt(2,4); // Set author ID placeholder valueint rowsUpdated = updateLastName.executeUpdate(); // execute the updateSystem.out.println(“Rows affected: “ + rowsUpdated);
Trang 28This should produce the following output:
Rows affected: 1
How It Works
The PreparedStatementobject is created from the Connectionobject by calling the
prepareStatement()method The statement is also defined with the placeholders marked as questionmarks Those placeholders, for the last name and author ID columns, respectively, are then filled withvalues at run time by calling the setString()and setInt()methods of the PreparedStatementinterface
The statement is executed by calling the executeUpdate()method, which returns the number of rowsaffected by the update operation No arguments are passed to the method since the SQL statement wasdefined when the PreparedStatementobject was created
Note that if no change was made to the database, which would occur for example if the primary keyvalue, authid, did not exist in the authorstable, the 0 would be returned as the number of rowsaffected If you want to verify that the change was made, you can use the InteractiveSQLprogram toinspect the list of authors
Statement versus PreparedStatement
There will be times where the choice between using a Statementobject or a PreparedStatementobject may not be entirely clear PreparedStatementobjects are great when:
❑ You need to execute the same statement several times and need to change only specific values
❑ You are working with large chunks of data that make concatenation unwieldy
❑ You are working with a large number of parameters in the SQL statement that make string catenation unwieldy
con-Conversely, Statementobjects work well when you have simple statements; and of course, you have nooption if your JDBC driver doesn’t support the PreparedStatementinterface
Working with Input Streams
One of the most intriguing features of the PreparedStatementinterface is the ability to use a stream asthe source of data to be inserted in a statement in place of a placeholder It’s very often more convenient
to deal with streams when you’re working with data types like LONGVARCHARand LONGVARBINARY Forexample, an application storing binary images can very efficiently populate a LONGVARBINARYcolumn
by creating a FileInputStreamobject representing the source file
The PreparedStatementinterface provides three methods for extracting data from input streams:
Trang 29Method Description
setAsciiStream() Use for columns with the SQL type LONGVARCHARsetUnicodeStream() Use for columns with the SQL type LONGVARCHARsetBinaryStream() Use for columns with the SQL type LONGVARBINARY
Each of these methods requires three argument values that indicate the placeholder position, theInputStreamobject that is the source of the data, and the number of bytes to be read from the stream If
an end of file is encountered before the designated number of bytes have been read, the methods throw
an exception of type SQLException.The next example is a simple illustration of using the setAsciiStream()method of thePreparedStatementto store Java source code in a database It opens a Java source code file as anInputStreamobject and uses that InputStreamobject to populate a column in the database Accessdoes not support the LONGVARCHARSQL type, and you have to use LONGTEXTas the type for the fieldthat will store the source code in the CREATE TABLEcommand
Try It Out PreparedStatement and Input StreamsThe program starts out with the usual code for establishing a connection to the database Then aFileInputStreamobject is created from the source code file for this program The number of bytes con-tained by the file is obtained by calling the available()method:
String url = “jdbc:odbc:technical_library”;
String driver = “sun.jdbc.odbc.JdbcOdbcDriver”;
String user = “guest”;
String password = “guest”;
FileInputStream fis = new FileInputStream(“TryInputStream.java”);
Trang 30String ins = “INSERT INTO source_code VALUES(?,?)”;
PreparedStatement statement = connection.prepareStatement(ins);
// Set values for the placeholdersstatement.setString(1, “TryInputStream”); // Set first fieldstatement.setAsciiStream(2, fis, fis.available()); // Stream is sourceint rowsUpdated = statement.executeUpdate();
System.out.println(“Rows affected: “ + rowsUpdated);
connection.close();
} catch (Exception e) {System.err.println(e);
}}
}
The code can throw exceptions of types IOException, ClassNotFoundException, and
SQLException, and they all need to be caught The FileInputStreamconstructor and the
setAsciiStream()method of the PreparedStatementinterface can throw IOExceptionexceptions.Since all you’ll do in each case is output the exception to the error stream, you can economize on thecode by catching them all in the same catchblock that uses Exceptionas the type This works becauseall exception objects have the Exceptionclass as a base
You might want to check your results by running the InteractiveSQLapplication to verify that thetable was created and the rows were inserted Start that application and execute the SQL statement:
SELECT * FROM source_code
After you’re satisfied with your results, feel free to delete the table Having restarted the
InteractiveSQLapplication, execute the statement:
DROP TABLE source_code
Note that once the table exists, executing the CREATE TABLEcommand will fail, so if you want to run theexample more than once be sure to delete the table each time
How It Works
This program is very similar to the previous example AFileInputStreamobject is created from thefile TryInputStream.java Since the setXXXStream()methods need to know how many bytes toread from the stream, you have to get the file size of the TryInputStream.javafile by calling theavailable()method of the FileInputStreamobject
Trang 31You first create the table by executing the CREATE TABLESQL command using the StatementobjectcreateTable Then the PreparedStatementobject statementis created, and the placeholder value forthe first column is set by calling the setString()method for the statementobject The real magic hap-pens in the setAsciiStream()method — all you have to do is supply the method with the placeholderposition, the InputStream, and the number of bytes to be read — returned by the available()methodfor the FileInputStreamobject When the SQLINSERTstatement is executed, the bytes are read fromthe stream and stored in the second column of the row inserted in the database table source_code.When your JDBC applications will be dealing with large chunks of data, the Streammethods of thePreparedStatementinterface are a real help.
The ResultSet
Now that you have a good understanding of the capabilities of the StatementandPreparedStatementinterfaces, it’s time to dig a little deeper into the details of getting the data backfrom the query In this section, you’ll add to what you learned about the ResultSetobject in the lastchapter You’ll explore the getXXX()methods in more depth and look at some of the special SQL datatypes and how they are handled You’ll also look at how to use streams with a ResultSetobject
Retrieving Column Data for Specified Data Types
So far you’ve retrieved data from a resultset as type Stringbecause data of any SQL type can beretrieved in this way As you saw briefly in the previous chapter, like the StatementandPreparedStatementinterfaces, the ResultSetinterface provides methods for working with a variety
of data types and retrieving data as a Java type that is more consistent with the original SQL type
Most of these methods work in a similar way and come in two overloaded forms One form specifies thecolumn by the column name:
xxxType resultSet.getXXX(String columnName)The other specifies the column name by its index position, the first position index being 1:
xxxType resultSet.getXXX(int columnPosition)The mechanics of calling these methods is quite straightforward, but to use these methods effectively,you need to understand the possible mappings between Java data types and SQL data types in bothdirections
Trang 32The table in Figure 25-3 illustrates the mappings between SQL data types and the appropriate
ResultSet getXXX()methods To decide which getXXX()method you should use, look in the table forthe method that maps the column data type to the Java type you’ll use The “preferred” method for a
type is indicated with the Y character That means that it is the closest mapping to the SQL type Other methods that may also work are indicated by the ± symbol.
Figure 25-3
Working with Null Values
As I’ve said, NULLis a special value in the world of SQL NULLis not the same thing as an empty stringfor text columns, nor is it the same thing as zero for a numeric field NULLmeans that no data is defined
ResultSet Method to SQL Data Type Mapping
Trang 33for a column value within a relation For example, recall the authorstable, which has several valuesthat may or may not have values assigned, including the emailcolumn To determine which authors donot have an e-mail address recorded, you could use the following query:
SELECT authid FROM authors WHERE email = NULLThis query will return the ID for each author without an e-mail address
The ResultSetinterface provides a method for testing a column value within a resultset to determine if
it is null The wasNull()method returns a booleanvalue that is trueif the last column read from theResultSetobject was a null, and falseif it was some other value
You’ll need to use the capability to detect a nullvalue for a field in your code unless you created yourtables with every column defined as NOT NULL, which tells the database system that it must never allow
a nullvalue in any column However, that’s not always a practical or desirable way to design tables.Let’s consider a simple example that selects and displays the author ID, last name, first name, and e-mailaddress for each row in the authorstable If any of these values are not assigned a value, the code couldthrow a NullPointerExceptionwhen the program attempts to display the value To avoid that sort ofbad program behavior, this example will use the wasNull()method of the ResultSetto check forempty fields Notice that the wasNull()method is called after the value is retrieved from theResultSet
Try It Out Testing for Null Values in the ResultSetHere’s the code for the example:
String driver = “sun.jdbc.odbc.JdbcOdbcDriver”;
String theStatement = “SELECT authid, lastname, firstname, email FROM authors”;try {
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, “guest”, “guest”);Statement queryAuthors = connection.createStatement();
ResultSet results = queryAuthors.executeQuery(theStatement);
String lastname, firstname, email;
Trang 34if(results.wasNull()) {email = “no email”;
}System.out.println(Integer.toString(id) + “, “ +
lastname.trim() + “, “ +firstname.trim() +”, “ +email.trim());
}queryAuthors.close();
} catch (Exception e) {System.err.println(e);
}}
}
Running this code produces the following results:
1, Gamma, Erich, no email
2, Helm, Richard, no email
3, Johnson, Ralph, no email
4, Horton, Ivor, no email
How It Works
In TestNullValues, the SQL statement is executed, and the values for the author ID, last name, firstname, and e-mail address are extracted into local variables The rows in the resultset are ordered byauthidbecause of the ORDER BYclause in the SQL query Because the value for emailcan be nullinthe table, you call the wasNull()method immediately after retrieving that column from resultsto test
if the value read was a nullvalue If so, you replace the literal string referenced by email, so outputtingthe report will work without throwing an exception Since the authid, lastname, and firstnamecolumns are required, there’s no need to test those column values for nullvalues
Working with Special Data Types
The JDBC java.sqlpackage defines some special data types to accommodate the characteristics of ticular SQL types that don’t readily map to a standard Java data type AResultSetclass object hasmethods for accessing data of these special types The ResultSetclass also defines methods that accessvalues of SQL types that map to Java data types defined in the java.mathpackage that are designed tohandle numbers with a large number of digits of precision These types are as follows:
❑ AtoString()method that formats the value of the date as a string in the form yyyy-mm-dd
❑ A static valueOf()method that converts a string representation (yyyy-mm-dd form) of a date
into a java.sql.Dateobject
Trang 35❑ AsetTime()method that accepts an argument of type longthat is a millisecond value relative
to 1st January 1970, 00:00:00 GMT, and sets the date value encapsulated by the currentjava.sql.Dateobject to this value
Time
Like java.sql.Date, the java.sql.Timeclass wraps the java.util.Dateclass as a subclass Theclass defines a static valueOf()method that returns a Timeobject from a string representation
(hh:mm:ss form) of time into a Timeobject and a toString()method that returns a string representation
of the time encapsulated by the object in the form hh:mm:ss.
Timestamp
The java.sql.Timestampclass also subclasses java.util.Date, but provides additional support forSQL timestamps with support for nanoseconds (java.util.Datesupports time only to the nearest mil-lisecond) The static valueOf()method creates a Timestampobject from a string representation (yyyy-
and after()— to support nanoseconds
Big Numbers
The SQLNUMERICand DECIMALtypes are mapped to the Java BigDecimalclass type This class isdefined in the java.mathpackage along with the BigIntegerclass that defines objects that encapsu-late integers of arbitrary precision, with negative values in 2’s complement form ABigDecimalobjectdefines a decimal value of arbitrary precision that can be positive or negative A BigDecimalobject isimplemented as an arbitrary precision signed integer — a BigIntegerobject, plus a scale value thatspecifies the number of digits to the right of the decimal point Thus, the value of a BigDecimalobject isthe integer value divided by 10scale To read a column value of either NUMERICor DECIMALSQL type as aJava BigDecimalobject, you use the getBigDecimal()method for the ResultSetobject
The BigIntegerand BigDecimalclasses are very useful for applications that require a large number ofdigits of precision, such as security keys, very large monetary values, and so forth The BigIntegerandBigDecimalclasses provide mathematical methods for addition, subtraction, multiplication, and divi-sion, as well as comparison methods and methods for returning their value as standard Java types.Additionally, BigIntegerobjects support bitwise and shift operations The BigDecimalclass providesmethods for tailoring the rounding behavior in arithmetic operations
Like Java Stringobjects, the value of a BigIntegeror BigDecimalobject is immutable That is, once
an object has been created, you can’t change its value When you apply arithmetic operations toBigIntegerand BigDecimalobjects using their methods, such as multiply()and divide(), youalways get a new object as a result, in much the same way as you get a new Stringobject when you usethe concat()or substring()methods of String
You can get an idea of the usefulness of these classes if you consider the difficulties you would have ifyou had to use type doublefor computations where which you needed to maintain a great deal of accu-racy Suppose you had to calculate the product of the following two floating-point numbers:
98765423462576235623562346234623462.35632456234567890and
9898234523235624664376437634674373436547.34586558
Trang 36If you were to calculate the product of two variables of type doublethat you have initialized with thesevalues, you would probably be very disappointed when your code produced the result:
9.776033242192577E74
Considering the number of digits of precision you entered originally for the factors, you could not claim that accuracy has been maintained Of course, the problem is that the precision for values of typedoubleis fixed and limited to the number of digits that you see in the preceding result Enter theBigDecimalclass Let’s see how it would work with that
Try It Out The BigDecimal Class
You can do the calculation and produce the sort of result that you want using BigDecimalobjects asfollows:
import java.math.BigDecimal;
public class TestBigDecimal {
public static void main(String[] args) {
BigDecimal bn1 = new BigDecimal(
How It Works
The BigDecimalclass has remarkable capabilities It can support numbers of virtually limitless
precision The precision and scale are both 32-bit signed integer values, so they can be as large as2,147,483,647 digits — and that’s a huge number of decimal digits! In the example, you create twoBigDecimalobjects, bn1and bn2, representing the original values that you want to multiply You multi-ply them using the multiply()method for bn1and store the reference to the BigDecimalobject that isreturned containing the result in bn3 You can use this in a println()method call since the
BigDecimalclass implements the toString()method
The BigDecimalclass defines methods that implement all of the usual arithmetic operators, add(),subtract(), multiply(), divide(), and remainder(), as well as a range of methods supportingother operations, including max(), min(), pow(), and compareTo() Just about anything that you can
do with a primitive numeric type you can also do with BigDecimalobjects
The BigIntegerclass is just as impressive — it provides the same arbitrary precision characteristics forinteger values Of course, there is a price to pay for that precision Computations using the BigIntegerand BigDecimalclasses are notably slower than their counterparts using native Java types
Trang 37The BigIntegerand BigDecimalclasses manage digits as objects in a vector, so to get the flexibility ofunlimited precision, you have to trade off computing time for operations on the numbers Nonetheless,this class is invaluable for many applications.
Working with Streams
Earlier, you looked at using streams to populate LONGVARCHARand LONGVARBINARYcolumns becauseit’s frequently much easier to use streams when you’re working with large objects
The ResultSetinterface provides three methods for retrieving data from a database as a stream Thesemethods are:
getCharacterStream() Use for LONGVARCHARcolumnsgetBinaryStream() Use for LONGVARBINARYcolumns
Each of these methods requires an argument to be supplied that indicates the column either by name or
by index position The getCharacterStream()method returns a reference to a Readerobject, whereasthe other two methods return a reference to an InputStreamobject from which you can read the datafor the column
The next example shows a simple example of using the getAsciiStream()method of the ResultSet.This example extends the TryInputStream.javaprogram that you saw in the previous chapter
Try It Out ResultSet Columns as StreamsHere’s a version of the TryInputStream.javaprogram that uses a stream:
String url = “jdbc:odbc:technical_library”;
String driver = “sun.jdbc.odbc.JdbcOdbcDriver”;
String user = “guest”;
String password = “guest”;
FileInputStream fis = new FileInputStream(“TryInputStream2.java”);
Class.forName(driver);
Trang 38Connection connection = DriverManager.getConnection(url, user, password);Statement createTable = connection.createStatement();
createTable.executeUpdate(
“CREATE TABLE source_code (name char(20), source LONGTEXT)”);
String ins = “INSERT INTO source_code VALUES(?,?)”;
PreparedStatement statement = connection.prepareStatement(ins);
statement.setString(1, “TryInputStream2”);
statement.setAsciiStream(2, fis, fis.available());
int rowsUpdated = statement.executeUpdate();
System.out.println(“Rows affected: “ + rowsUpdated);
// Create a statement object and execute a SELECTStatement getCode = connection.createStatement();
ResultSet theCode = getCode.executeQuery(
“SELECT name,source FROM source_code”);
BufferedReader reader = null; // Reader for a columnString input = null; // Stores an input linewhile(theCode.next()) { // For each row
// Create a buffered reader from the stream for a columnreader = new BufferedReader(
new InputStreamReader(theCode.getAsciiStream(2)));// Read the column data from the buffered reader
while((input = reader.readLine()) != null) { // While there is a lineSystem.out.println(input); // display it
}}connection.close();
} catch (Exception e) {System.err.println(e);
}}
}
Make sure that the source_codetable doesn’t exist before you run the program If it does, you candelete it using the InteractiveSQLprogram, as you’ve seen before You should see the text of thissource code printed out After you’re satisfied with your results, you can delete the table using theInteractiveSQLapplication
How It Works
Most of this code sets up and populates the source_codetable with the data from the program sourcefile, TryInputStream2.java Once that is done you get a Statementobject from the connection thatyou’ll use to retrieve the data from the table The whileloop iterates through all the rows in the resultsetthat contains the result of the SQL query in the way that you are now very familiar with
Using the ResultSetobject that is returned from executeQuery(), you get an InputStreamobjectcorresponding to the second column in the current row by calling its getAsciiStream()method withthe position index argument as 2 The InputStreamobject that is returned is used to create an
InputStreamReaderobject, which in turn is used to create a BufferedReaderobject that providesbuffered stream input for the column data The readLine()method for the BufferedReaderobject
Trang 39returns a Stringobject containing a line of input When the end of the stream is reached, thereadLine()method returns nullso the inner whileloop will then terminatẹ
Calling Procedures
Many database systems support stored procedures, which are predefined sequences of SQL commands
that you can call when you want to execute the function that the stored procedure defines This is a verypowerful facility with a lot of ramifications, so I’ll only touch on the basics of how to use this here, just
so that you are aware of it JDBC provides support for this sort of capability through thejavạsql.CallableStatementinterface, which is derived from the PreparedStatementinterfacẹYou can obtain a CallableStatementreference corresponding to a stored procedure call by calling theprepareCall()method for a Connectionobject
The argument to the prepareCall()method is a Stringobject that defines a string in a format
described as SQL escape syntax The purpose of SQL escape syntax is to enable the driver to determine
that this is not an ordinary SQL statement and needs to be transformed into a form that will be stood by the database system This enables the idiosyncrasies of how stored procedures are called in dif-ferent database systems to be accommodated, since it is up to the driver to recognize that the string isSQL escape syntax and transform it to the format required by the underlying databasẹ Let’s first con-sider the simplest possible form for a string to call a stored procedure:
under-“{call procedureName}”
The braces are always present The procedure to be called has the name procedureNamẹ Given that youhave a Connectionobject connection, you could call the procedure with the following code:
CallableStatement call = connection.prepareCall(“{call procedureName}”);
ResultSet result = call.executeQuery(); // Execute the procedure callNow you can obtain the data from the ResultSetobject that is returned by the procedure usinggetXXX()methods in the usual waỵ This code assumes that the stored procedure produces a singleresultset Of course, it is possible that a procedure may produce multiple resultsets, in which case youwould use the execute()method to execute the procedure and getResultSet()to retrieve the result-set If the procedure updated the database, you would call the executeUpdate()method to execute it.Stored procedures can have arguments that specify input values (called INparameters) to the operation
In this case you specify the parameter list between parentheses following the procedure namẹ Eachparameter is denoted by ?, as for a PreparedStatementcommand, and you set the values for theparameters using the setXXX()methods in the way you have seen for PreparedStatementobjects Forexample:
CallableStatement call = connection prepareCall(“{call getMonthDatẳ, ?)}”);call.setInt(1, 6); // Set first argument valuecall.setInt(2,1999); // Set second argument valueResultSet result = call.executeQuery(); // Execute the procedure call
As you have seen, each parameter is identified by an index value, the first parameter having the indexvalue 1
Trang 40Procedures can also have parameters for returning a result — referred to as OUTparameters — and youcan set these, toọ The placeholder for an OUTparameter is ?— no different from an INparameter — butobviously the process for setting the parameter is significantly different For each OUTparameter, youmust identify the type of the output value as one of the types defined in the javạsql.Typesclass bycalling the registerOutParameter()method for the CallableStatementobject The first argument
is the index position of the OUTparameter, and the second argument is the typẹ For example:
call.registerOutParameter(2, Types.INTEGER); // Second parameter OUT and INTEGER
Of course, if you don’t want to qualify the type constants from the Typesclass in the code, you canalways use a static importstatement to import the names into your source filẹ
Once the OUTparameters have been registered, you then execute the procedure call in the way you haveseen If a resultset is returned, you can access the data from that in the usual waỵ To get the value foreach OUTparameter, you must call the getXXX()method for the CallableStatementobject that corre-sponds to the parameter type, so for the preceding example you would write:
int value = call.getInt(2); // Read second parameter value
Procedures can also have parameters that serve as both input and output, so-called INOUTparameters
In this case you just combine what I’ve discussed for INand OUTparameters, using setXXX()for theinput value, registerOutParameter()to set the type for the output, and getXXX()to retrieve theoutput valuẹ
Finally, a stored procedure may return a value — not as part of the parameter list but as a return value asfor a method In this case you specify it as follows:
CallableStatement call = connection.prepareCall(“{? = call getDatẳ, ?)}”);
This has two parameters plus a return value specified by the first ?— preceding the =in the string Thisplaceholder is at index position 1, and the two other parameters will be at index position 2 and 3 Younow need to register the type of the value that is returned by the procedure before you execute the pro-cedure call You do this in the same way as for any other OUTparameter:
warn-Unfortunately, life is a bit less predictable than that, and you need to take some extra steps in your JDBCapplications to handle conditions that generate warnings or errors In this section, you’ll see how tobuild mechanisms to trap errors, how to use the extra facilities built into JDBC to get detailed warning