Java 1.1 Unleashed
- 43 -
Getting Started with JDBC
by Shelley Powers
IN THIS CHAPTER
- Connecting to a Database and Making Simple Queries
- More Complex Uses of JDBC
- Working with Multiple Databases
The Java Database Connectivity (JDBC) API includes classes that can handle most
database access needs. From simple select statements to processing the results of
a complex stored procedure to accessing more than one database at a time, the JDBC
provides the functionality most applications need.
This chapter provides examples that use the main JDBC classes. For general information
about the classes, review Chapter 19, "The SQL Package." Additionally,
Chapter 42, "Databases and Java," provides information about accessing
data using techniques other than the JDBC.
To use the JDBC, you must have one or more JDBC drivers written specifically to
connect to the database types you are using. A list of these drivers can be found
at the Sun JavaSoft site at http://www.javasoft.com/jdbc.
Connecting to a Database and Making Simple Queries
One very attractive aspect of the JDBC is how easy it is to use. To connect to
a database, make a query, and process the results takes no more than a few lines
of code, as the following sections demonstrate.
THE SAMPLES IN THIS CHAPTER
The samples in this chapter use either the JDBC-ODBC bridge that comes with
the Java Development Kit or the Imaginary mSQL-JDBC driver. Three databases are used:
a Sybase SQL Anywhere database, a Microsoft Access 97 database, and an mSQL database.
The databases and the script to make the databases can be found on the CD-ROM that
accompanies this book.
Database Connections
The database connection string is the only JDBC component that references the
particular database being accessed. You must provide a specific driver connection
string that uses the protocol defined for the driver. These protocols, currently
maintained by JavaSoft, are used by the JDBC DriverManager class to determine
which driver to load.
Remember to establish the database connection with the ODBC Administrator before
using these sample programs. For example, to connect to a Sybase SQL Anywhere database
using the JDBC-ODBC bridge, use the following connection string and connection method:
//connect to ODBC database
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
The second line of the code creates a String object that contains the
URL for the database. The string also includes the protocol jdbc, the subprotocol
odbc, and the location of the database Zoo. The fourth line creates
the Connection object for the database. Because this example uses the JDBC-ODBC
bridge, the database access is through the name defined with the ODBC Administration
tool, found in the Control Panel group for Windows NT or 95.
Compare this example with one using the mSQL-JDBC driver and note that the URL
string differs in format as well as in content:
//connect to database
Class.forName("imaginary.sql.iMsqlDriver");
String url = "jdbc:msql://yasd.com:1112/test";
// connect
Connection con = DriverManager.getConnection(url);
With this driver, the subprotocol is msql, and the database connection
information required by the driver is a double slash followed by the name of the
site, the port, and the name of the database. The port number 1112 specifically
tells the mSQL-JDBC driver that the database is being run by root rather
than by some other user name. Specifying a port number of 4333 states that
the database is running under another user name, which should also be specified in
the connection.
In these two examples, note that the second example loads the driver classes explicitly
using the Class.forName() method. With this method, the class loader checks
for the driver classes in the local path designated by the CLASSPATH variable.
If the driver classes are not found and the method is called from an applet, the
class loader attempts to download them from the network. Chapter 44, "Building
Database Applets with JDBC," provides more information about classes and applets.
Another difference between the two examples is that the first connection, for
the JDBC-ODBC bridge, uses three parameters: The connection string, the database
user name, and the password. The second example, for the mSQL-JDBC driver, passes
just the connection string and assumes that the connection is open to all. A third
example, which follows (and which also uses the JDBC-ODBC bridge), shows a connection
that uses the third getConnection() method, which uses a Properties
class:
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Properties p = new Properties();
p.put("user", "dba");
p.put("password","sql");
Connection con = DriverManager.getConnection(url,p);
The benefit of this third technique is that the application can prompt the user
for the user name and password so that the password can be entered into an encrypted
field and not be hard coded into the application.
Listing 43.1 shows the complete code for connecting to an ODBC database and processing
a simple query. Note that, at the end of the application, the Connection
instance is closed. Unless resources are limited, closing the Connection
instance is usually not necessary. When the object is no longer in scope (as is the
case when the application is no longer running, the applet is unloaded, or the object
is contained within a procedure or control structure that ends), normal Java garbage
collection frees up the resources.
Listing 43.1. The sample1 application using JDBC.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample1
{
public static void main(String arg[]) {
int id;
float amount;
Date dt;
String status;
String result;
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
// create Statement object
Statement stmt = con.createStatement();
String sqlselect = "Select company_id, order_dt, po_amount, status_cd"
+ " from dba.purchase_order";
// run query
ResultSet rs = stmt.executeQuery(sqlselect);
// process results
while(rs.next()) {
result = "";
id = rs.getInt(1);
amount = rs.getFloat(3);
dt = rs.getDate(2);
status = rs.getString(4);
result = result.valueOf(id) + " ";
result+= result.valueOf(amount) + " ";
result+= dt.toString() + " " + status;
System.out.println("Values are: " + result);
}
//close connection
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
Once a connection is established to the database, it is used to create some kind
of statement using the Statement class or one of its extensions.
The Statement Class
A statement for simple querying of a database takes the following form:
// create Statement object
Statement stmt = con.createStatement();
This type of statement can be used for single-use updates or queries to the database
that do not return multiple result sets or update counts.
After the statement is created, it can be used to issue a simple query, as shown
here:
String sqlselect = "Select company_id, order_dt, po_amount, status_cd"
+ " from dba.purchase_order";
// run query
ResultSet rs = stmt.executeQuery(sqlselect);
This example shows how a String object is used to hold the select statement,
which is then used in the executeQuery() method call. This method, in turn,
generates a ResultSet object containing the results of the query.
The preceding example shows a select statement that accesses all the data in a
table. Parameters passed to the Java application can be appended to the SQL statement
for a more dynamic query, as shown here:
String srch = arg[0];
String sqlselect = "Select company_id, order_dt, po_amount, status_cd"
+ " from dba.purchase_order where company_id = " + srch;
// run query
ResultSet rs = stmt.executeQuery(sqlselect);
In addition to searching for specific values, wildcard characters (also referred
to as escape or pattern match characters) can be used in a search. In Java, an underscore
character (_) is used to search for a single character, and a percent sign
(%) is used to search for zero or more characters, as shown in Listing 43.2.
Listing 43.2. The sample2 application using escape characters.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample2
{
public static void main(String arg[]) {
int id;
float amount;
Date dt;
String status;
String result;
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
// create Statement object
Statement stmt = con.createStatement();
String srch = arg[0];
String sqlselect = "Select company_id, order_dt, po_amount, status_cd"
+ " from dba.purchase_order where company_id = " + srch;
// run query
ResultSet rs = stmt.executeQuery(sqlselect);
// process results
while(rs.next()) {
result = "";
id = rs.getInt(1);
amount = rs.getFloat(3);
dt = rs.getDate(2);
status = rs.getString(4);
result = result.valueOf(id) + " ";
result+= result.valueOf(amount) + " ";
result+= dt.toString() + " " + status;
System.out.println("Values are: " + result);
}
//close connection
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
This code in Listing 43.2 looks for any company name that contains the value specified
by the variable srch. Notice that the results are pulled from the row using
the data type specific to the column. These values are then converted to String
values using the valueOf() method. A more efficient method would have been
to pull the values in directly as strings using the getString() method,
but the example does demonstrate some of the other ResultSet get methods.
CAUTION: If you are using the escape characters,
don't forget to include the surrounding quotation marks (") for the pattern-match
sequence. Pattern matching is for character-based or like fields; if you forget the
quotation marks, you will get an error.
You can also run update statements with the JDBC. Instead of using executeQuery(),
your application must use executeUpdate().
The update statement can be any valid data modification statement: UPDATE,
DELETE, or INSERT. The result is the count of rows impacted by
the change. Listing 43.3 shows an example of each of the different types of data
manipulation statements.
Listing 43.3. The sample3 application demonstrating database
updates.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample3
{
public static void main(String arg[]) {
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
con.setAutoCommit(false);
// create Statement object
Statement stmt = con.createStatement();
String updatestring = "INSERT INTO RETAIL_ITEM(item_nbr,item_desc, qty_per_pkg, "
+ "wholesale_cost, retail_cost, company_id, color, size) "
+ "VALUES(25,'some item', 1, 10.00, 15.00, 1, 'orange', 'M')";
// run query
int ct = stmt.executeUpdate(updatestring);
// process results
System.out.println("Insert row: " + updatestring.valueOf(ct));
updatestring = "UPDATE PERSON "
+ "set zip_cd = '97228' where "
+ "zip_cd = '97229'";
// run query
ct = stmt.executeUpdate(updatestring);
// process results
System.out.println("Updated rows: " + updatestring.valueOf(ct));
//back out modifications
updatestring = "DELETE FROM RETAIL_ITEM "
+ "where item_nbr = 25 and company_id = 1";
// run query
ct = stmt.executeUpdate(updatestring);
// process results
System.out.println("Delete row: " + updatestring.valueOf(ct));
updatestring = "UPDATE PERSON "
+ "set zip_cd = '97229' where "
+ "zip_cd = '97228'";
// run query
ct = stmt.executeUpdate(updatestring);
// process results
System.out.println("Updated rows: " + updatestring.valueOf(ct));
//close connection
con.commit();
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
-
TIP: In Listing 43.3, note that autocommit
is turned off for the set of transactions. If you don't turn autocommit
off, each of the transactions would be committed as soon as it completed, rather
than committing after all the transactions have completed. Because we are backing
changes out, we want all the transactions to succeed before issuing a commit; otherwise
all the transactions will roll back if an exception occurs (the database implements
this functionality by default).
The Statement object can also be used in a call to a database stored
procedure if no dynamic parameters are given. For procedures with IN, OUT,
and INOUT parameters, the CallableStatement class is used, as explained
later in this chapter.
If the statement is a query, it always returns a ResultSet object unless
an exception occurs. The ResultSet object is demonstrated in more detail
in the following section.
The ResultSet Class
A query returns a ResultSet object. This object contains the results
of the query in a form that can be accessed by the application. If the query has
no results, the ResultSet object contains no rows; otherwise, it contains
rows of data matching the query (up to the limit specified for the database). If
the database supports doing so, you can set the maximum number of rows using the
Statement class method setMaxRows(). Once the result set is returned,
you can use methods to access and process individual columns as shown in Listing
43.4.
Listing 43.4. The sample4 application with a result set.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample4
{
public static void main(String arg[]) {
int id;
String name;
String address;
String city;
String state;
String result;
String srch = arg[0];
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
// create Statement object
Statement stmt = con.createStatement();
stmt. setMaxRows(10);
String sqlselect = "Select company_id, company_name, address_1, city, state_cd"
+ " from dba.company where company_name like '%" + srch + "%'";
// run query
ResultSet rs = stmt.executeQuery(sqlselect);
// process results
while(rs.next()) {
result = "";
id = rs.getInt(1);
name = rs.getString(2);
address = rs.getString(3);
city = rs.getString(4);
state = rs.getString(5);
result = result.valueOf(id) + " ";
result+= name + " " + address + " ";
result+= city + " " + state;
System.out.println("Values are: " + result);
}
//close connection
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
Individual data types have matching getXXX() methods to retrieve
the value in the form the application prefers. You pass to each method the number
of the column representing the position it holds in the original select statement,
or a text string containing the name of the column. The next() method maintains
a cursor that points to the current row being processed. Each call to the method
moves the cursor to the next row. When no more rows are found, the next()
method returns a value of false.
You can access the columns even if you don't know the order in which the columns
will be returned. Using the findColumn() method, you can look for a column
with the same name as the one passed to the method; the method returns the column
index. You can then use the index used to access the value, as shown in Listing 43.5.
Listing 43.5. The sample5 application using the findColumn()
method.
// run query
ResultSet rs = stmt.executeQuery(sqlselect);
// process results
while(rs.next()) {
result = "";
id = rs.getInt(rs.findColumn("company_id"));
name = rs.getString(rs.findColumn("company_name"));
address = rs.getString(rs.findColumn("address_1"));
city = rs.getString(rs.findColumn("city"));
state = rs.getString(rs.findColumn("state_cd"));
result = result.valueOf(id) + " ";
result+= name + " " + address + " ";
result+= city + " " + state;
System.out.println("Values are: " + result);
}
This technique is a little convoluted because you can also use the get methods
that accept a string representing the column name and return the result. A partial
listing of the results of running the sample5 Java application are shown
here:
java sample5
Values are: 1 Portland T-Shirt Company 18050 Industrial Blvd. Portland, OR
Values are: 2 Tri-State Stuffed Critter 923 Hawthorne Way Vancouver WA
Values are: 3 Tigard Candy Shop 1900 Mountain Rd Tigard OR
Values are: 4 LA T-Shirt Company 13090 SW 108th Ave SW Los Angeles CA
. . .
In addition, you can also check to see whether a column value is null
by using the wasNull() method after the getXXX() method
call:
state = rs.getString("state_cd");
boolean b = rs.wasNull();
if (b) {
. . .
The examples in this section introduced and demonstrated the Connection,
Statement, and ResultSet classes. The next two sections demonstrate
the use of some more complex features of the JDBC and how to access multiple heterogeneous
databases.
More Complex Uses of JDBC
Occasionally, a database developer has to program for more complex database access
situations. You may want to create a statement and then execute it many times, or
call a stored procedure that returns multiple result sets, or issue a dynamic SQL
statement. This section covers some techniques for handling these types of statements.
Two of the classes discussed, PreparedStatement and CallableStatement,
are extended classes: the former is an extension of Statement and the latter
is an extension of PreparedStatement.
In addition to the classes, the following sections also demonstrate and discuss
the execute() method of the Statement class.
The PreparedStatement Class
The PreparedStatement class is used to create and compile a statement
at the database, and then invoke that statement multiple times. The statement usually
has one or more IN parameters that change each time the statement is executed.
Both the executeUpdate() and executeQuery() methods work with
the PreparedStatement class. The class is an extension of the standard Statement
class; it has the additional flexibility of being stored in a compiled form and run
many times.
To create the PreparedStatement class statement, use a SQL string that
contains references to one or more unknown parameters:
String sqlselect =
"Select * from retail_item where company_id = ?";
// create Statement object
PreparedStatement stmt = con.prepareStatement(sqlselect);
Before executing the statement (in this example, it is a query), you must set
the IN parameter value:
stmt.setInt(1,i);
rs = stmt.executeQuery();
After executing the query or update, you can process the results in the same way
you process a regular statement:
int colcount = rsmd.getColumnCount();
// process results
while(rs.next()) {
result = "";
for (int k = 1; k <= colcount; k++) {
result+= rs.getString(k) + " ";
}
System.out.println("Values are: " + result);
}
The only difference between using the PreparedStatement class and the
Statement class is that the same statement would then be processed for the
former with different parameters.
A full example of using this class is given in Listing 43.6.
Listing 43.6. The sample6 application using the PreparedStatement
class.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample6
{
public static void main(String arg[]) {
String result;
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
con.setAutoCommit(false);
String sqlselect =
"Select * from retail_item where company_id = ?";
// create Statement object
PreparedStatement stmt = con.prepareStatement(sqlselect);
ResultSet rs;
ResultSetMetaData rsmd;
for (int i = 1; i <= 3; i++) {
stmt.setInt(1,i);
rs = stmt.executeQuery();
rsmd = rs.getMetaData();
int colcount = rsmd.getColumnCount();
// process results
while(rs.next()) {
result = "";
for (int k = 1; k <= colcount; k++) {
result+= rs.getString(k) + " ";
}
System.out.println("Values are: " + result);
}
}
//close connection
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
Issuing an update instead of a query is no different except that the IN
parameters are used to modify data instead of to select it, and that the executeUpdate()
method is used instead of executeQuery():
String sqlselect =
"Update retail_item set company_id = ? where company_id = ?";
// create Statement object
PreparedStatement stmt = con.prepareStatement(sqlselect);
for (int i = 1; i <= 3; i++) {
stmt.setInt(1,i);
stmt.setInt(2, i + 1);
int count = stmt.executeUpdate();
. . .
The purpose of the PreparedStatement class is to compile the query ahead
of time, which cuts back on the time necessary to process each query or update. If
you are not planning to run the SQL statement multiple times, using the PreparedStatement
class is not an effective approach.
PROBLEMS WITH PreparedStatement
The PreparedStatement class may not work if the database does not maintain
an open connection between transactions; if the database does not support the use
of compiled SQL; or if the JDBC drivers or the database drivers do not support this
type of statement. If you use the statement with a JDBC driver that doesn't support
it, you get a class exception. If you use the statement with a database or driver
that does not support it, you get a SQLException. In my experience, the
PreparedStatement class did not work with the ODBC driver for Sybase SQL
Anywhere; it also did not work with the driver I had for Microsoft Access at the
time this chapter was written. Using the PreparedStatement class resulted
in the error Invalid Cursor State, as shown here:
java sample6
Values are: 1 Eagle T-Shirt 1 8.50 14.95 1 Black S null
Values are: 4 Wolf T-Shirt 1 8.50 14.95 1 Green S null
Values are: 5 Wolf T-Shirt 1 8.50 14.95 1 Green M null
Values are: 6 Wolf T-Shirt 1 8.50 14.95 1 Green L null
Values are: 19 Snake Shirt 1 13.50 22.00 1 Green S null
Values are: 20 Snake Shirt 1 13.50 22.00 1 Green M null
Values are: 21 Snake Shirt 1 13.50 22.00 1 Green L null
Values are: 22 Cat Shirt 1 8.00 13.00 1 Green XLG null
Values are: 2 Eagle T-Shirt 1 8.50 14.95 1 Black M null
Values are: 3 Eagle T-Shirt 1 8.50 14.95 1 Black L null
Values are: 7 Wolf T-Shirt 1 8.50 14.95 1 Blue L ADSDFS
Values are: 23 Get Wild Tie Dyed T-Shirt 1 10.50 20.95 1 null XL null
Values are: 24 Leopard T-Shirt 1 9.00 14.95 1 Brown L null
Values are: 30 Test 1 12.00 13.00 1 Orange S null
Values are: 40 Cats of the World T-Shirt 1 8.00 14.00 1 Black L null
[Sybase][ODBC Driver]Invalid cursor state
The execute() Method
The execute() method is defined for use with the Statement class,
which also makes it available for use with the PreparedStatement and CallableStatement
classes. This method is used to process an unknown statement, a statement that may
return multiple result sets and update counts, or both.
For example, you may have an ad-hoc SQL tool that allows the user to enter any
valid SQL statement and then process the statement. The user passes in the SQL statement
with a program call like this:
// create Statement object
Statement stmt = con.createStatement();
String sqlstmt = arg[0];
// run statement
boolean b = stmt.execute(sqlstmt);
The result returned from the execute() method is a boolean value:
it is false if there are no results or the statement contains an update;
it is true if the statement returns at least one result set and no update
counts.
If your application cares only about processing result sets from a statement,
you can process the results as follows:
// if true, result set
result = "";
if (b) {
// process results
ResultSet rs = stmt.getResultSet();
ResultSetMetaData rsmd = rs.getMetaData();
int colcount = rsmd.getColumnCount();
while(rs.next()) {
result = "";
for (int i=1; i <= colcount; i++) {
result+= rs.getString(i) + " ";
}
System.out.println("Values are: " + result);
}
}
. . .
Normally, however, your application wants to process all the return results--if
only to provide feedback to the user. Listing 43.7 provides a complete example of
using the execute() method.
Listing 43.7. The sample7 application using the execute()
method.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample7
{
public static void main(String arg[]) {
String result;
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
// create Statement object
Statement stmt = con.createStatement();
String sqlstmt = arg[0];
// run statement
boolean b = stmt.execute(sqlstmt);
// if true, result set
result = "";
if (b) {
// process results
ResultSet rs = stmt.getResultSet();
ResultSetMetaData rsmd = rs.getMetaData();
int colcount = rsmd.getColumnCount();
while(rs.next()) {
result = "";
for (int i=1; i <= colcount; i++) {
result+= rs.getString(i) + " ";
}
System.out.println("Values are: " + result);
}
}
else {
int ct = stmt.getUpdateCount();
result = "Update count is " + result.valueOf(ct);
System.out.println(result);
}
//close connection
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
Running the example and passing in the string "Select * from retail_item"
returns this result (only the first few lines of the result are shown because the
result is fairly lengthy):
java sample7 "select * from retail_item"
Values are: 1 Eagle T-Shirt 1 8.50 14.95 1 Black S null
Values are: 2 Chocolate Tigers 5 1.00 1.50 3 null M null
Values are: 3 Stuffed Panda 1 13.50 21.00 2 null L null
Values are: 4 Wolf T-Shirt 1 8.50 14.95 1 Green S null
Values are: 5 Wolf T-Shirt 1 8.50 14.95 1 Green M null
Values are: 6 Wolf T-Shirt 1 8.50 14.95 1 Green L null
Values are: 3 Keychain Zoo Pen 1 .52 1.95 8 null null null
Values are: 8 Taffy Pulls 3 .75 1.25 3 null null null
Values are: 9 Chocolate Pandas 5 1.00 1.50 3 null null null
Values are: 10 Stuffed Giraffe 1 9.95 16.95 2 null null null
An application rarely wants to process an unknown statement, but you may want
to run a stored procedure that has multiple result sets. This type of procedure is
demonstrated in the next section.
PROBLEMS WITH Using Cursors
The execute() method requires the database to support cursors, which
enable processing of multiple result sets. The execute() method also requires
the database and database driver to support maintaining an open connection after
a transaction. If the database does not support these features, the execute()
method can result in an error such as invalid cursor state (as happened
when I ran the sample7 application with both the Sybase SQL Anywhere ODBC
driver and the Microsoft Access ODBC driver). Before spending time coding for something
that may not work, test your JDBC and database drivers with the sample7
code in Listing 43.7.
The CallableStatement Class
The CallableStatement class is used to accept several parameters for
a stored procedure call. The parameters can be defined as input only, output only,
or both.
CallableStatement is an extension of PreparedStatement, which
is itself an extension of Statement. The CallableStatement class
adds methods to register and access output parameters.
To create a CallableStatement object, issue the procedure call with question
marks (?) in place of parameters:
String scall = "call new_po(?,?,?,?,?,?,?,?,?,?)";
// create Statement object
CallableStatement stmt = con.prepareCall(scall);
You must first set the IN parameters using the appropriate setXXX()
methods:
stmt.setInt(1, 1);
stmt.setDate(2, dt);
stmt.setInt(3,1);
stmt.setDouble(4, 10.00);
stmt.setString(5,"OP");
stmt.setInt(6,1);
stmt.setInt(7,1);
stmt.setDouble(8, 10.00);
stmt.setInt(9,61);
The last parameter is not set because it is an OUT parameter only. It
must be registered using one of the RegisterOutParameter() methods:
stmt.registerOutParameter(10,java.sql.Types.INTEGER);
Because the new_po() procedure contains two INSERT commands
and multiple result sets, in addition to the one output parameter, the execution
method to use for this statement is execute():
stmt.registerOutParameter(10,java.sql.Types.INTEGER);
// run statement
stmt.execute();
The result sets and update counts are then processed. Listing 43.8 shows all the
code that demonstrates the use of CallableStatement with two insert
commands.
Listing 43.8. The sample8 application using the CallableStatement
class.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample8
{
public static void main(String arg[]) {
String result;
Date dt = new Date(97, 12,1);
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
String scall = "call new_po(?,?,?,?,?,?,?,?,?,?)";
// create Statement object
CallableStatement stmt = con.prepareCall(scall);
stmt.setInt(1, 1);
stmt.setDate(2, dt);
stmt.setInt(3,1);
stmt.setDouble(4, 10.00);
stmt.setString(5,"OP");
stmt.setInt(6,1);
stmt.setInt(7,1);
stmt.setDouble(8, 10.00);
stmt.setInt(9,61);
stmt.registerOutParameter(10,java.sql.Types.INTEGER);
// run statement
stmt.execute();
// get update counts
boolean cont = true;
while(cont) {
int colcount = stmt.getUpdateCount();
if (colcount > 0) {
System.out.println("Updated rows are: " + colcount);
stmt.getMoreResults();
}
else {
cont = false;
}
}
cont =true;
result="";
while(cont) {
ResultSet rs = stmt.getResultSet();
ResultSetMetaData rsms = rs.getMetaData();
int colcount = rsms.getColumnCount();
for (int i = 1; i <= colcount; i++) {
result+=rs.getString(i) + " ";
}
System.out.println("results are " + result);
result="";
cont = stmt.getMoreResults();
}
int po_count = stmt.getInt(10);
System.out.println("Number of POS is " + po_count);
//close connection
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
Unfortunately, when I use this code with the JDBC-ODBC bridge to access a Sybase
SQL Anywhere database, I get the Function Sequence Error error:
java sample8
Updated rows are: 1
[Microsoft] [ODBC Driver Manager] Function sequence error
This ODBC DriverManager error occurs because an asynchronously executing
function is called when the function is still running from the first call. According
to the ODBC documentation, this error occurs when a procedure call or SQL execution
statement occurs while a previous call still requires data to be passed in from parameters.
After examining the data in the tables, however, I did find that the updates had
occurred.
As a workaround to this problem, I modified the code (the modified example is
in sample8b.java) to call a stored procedure that contains only the two
updates and not the multiple result sets, as shown in Listing 43.9.
Listing 43.9 The sample8b.java application containing the
stored procedure call with two updates.
import java.lang.*;
import java.sql.*;
import sun.jdbc.odbc.*;
public class sample8b
{
public static void main(String arg[]) {
String result;
Date dt = new Date(97, 12,1);
try {
//connect to ODBC database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:Zoo";
// connect
Connection con = DriverManager.getConnection(url,"dba", "sql");
String scall = "call new_po_copy(?,?,?,?,?,?,?,?,?,?)";
// create Statement object
CallableStatement stmt = con.prepareCall(scall);
stmt.setInt(1, 1);
stmt.setDate(2, dt);
stmt.setInt(3,1);
stmt.setDouble(4, 10.00);
stmt.setString(5,"OP");
stmt.setInt(6,1);
stmt.setInt(7,1);
stmt.setDouble(8, 10.00);
stmt.setInt(9,62);
stmt.registerOutParameter(10,java.sql.Types.INTEGER);
// run statement
stmt.executeUpdate();
int po_count = stmt.getInt(10);
System.out.println("Number of POS is " + po_count);
//close connection
con.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
This procedure is then used in an executeUpdate() call. The output parameter
is printed after the update occurs.
This version of the application worked without error and printed the number of
purchase orders in the database, as returned in the OUT parameter.
-
TIP: Chapter 42, "Databases and Java,"
describes a database utility tool, dbutil, that you can modify to get information
about what the database, driver, and JDBC driver can process (such as multiple result
sets). You should modify this tool to check what your database and drivers can do
before you code your application.
Working with Multiple Databases
The last Java application created in this chapter is one that refreshes a table
in an mSQL database with the contents of a table in a Microsoft Access database.
This example demonstrates the ease with which you can open and maintain multiple
database connections at the same time.
The first part of the code loads the mSQL-JDBC driver classes and opens a connection
to the mSQL database:
//connect to mSQL database
Class.forName("imaginary.sql.iMsqlDriver");
String url = "jdbc:msql://yasd.com:1112/yasd";
// mSQL connection
Connection con = DriverManager.getConnection(url);
// mSQL statement
Statement stmt = con.createStatement();
Next, an update is issued to delete the contents from the table being refreshed,
in this case, the company table:
// clean out existing data
stmt.executeUpdate("DELETE from company");
The Access database connection and statement is created next. Note that the mSQL
database is remote and that the Access database is local to the application:
//connect to Access database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
url = "jdbc:odbc:cityzoo";
// Access Connection
Connection con2 = DriverManager.getConnection(url,"Admin","sql");
// Access Statement
Statement stmt2 = con2.createStatement();
ResultSet rs = stmt2.executeQuery("Select company_id,"
+ "company_name, address_1, address_2, address_3,"
+ "city, state_cd, country_cd, postal_cd, phone_nbr "
+ "from company");
The data is pulled from the Access database as strings, which are then concatenated
in to an insert string for the mSQL database, as shown in Listing 43.10.
Listing 43.10. The code for sample9, which accesses two
different databases at the same time.
import java.sql.*;
import sun.jdbc.odbc.*;
import java.io.*;
public class sample9
{
public static void main(String arg[]) {
String invalue;
String outvalue;
String id, name, add1, add2, add3,city,state,country,post,phone;
int pos, endpos;
try {
System.out.println("Refreshing company...");
//connect to mSQL database
Class.forName("imaginary.sql.iMsqlDriver");
String url = "jdbc:msql://yasd.com:1112/yasd";
// mSQL connection
Connection con = DriverManager.getConnection(url);
// mSQL statement
Statement stmt = con.createStatement();
// clean out existing data
stmt.executeUpdate("DELETE from company");
//connect to Access database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
url = "jdbc:odbc:cityzoo";
// Access Connection
Connection con2 = DriverManager.getConnection(url,"Admin","sql");
// Access Statement
Statement stmt2 = con2.createStatement();
ResultSet rs = stmt2.executeQuery("Select company_id,"
+ "company_name, address_1, address_2, address_3,"
+ "city, state_cd, country_cd, postal_cd, phone_nbr "
+ "from company");
// for each line, enter into database
while (rs.next()) {
// get id
id = rs.getString(1);
name = rs.getString(2);
add1= rs.getString(3);
add2=rs.getString(4);
add3=rs.getString(5);
city=rs.getString(6);
state=rs.getString(7);
country=rs.getString(8);
post=rs.getString(9);
phone=rs.getString(10);
// create and execute insert statement
stmt.executeUpdate("insert into company (company_id, company_name,"
+ "address_1,address_2,address_3, "
+ "city,state_cd,country_cd,postal_cd,phone_nbr) values("
+ id + ",'" + name + "','" + add1 + "','"
+ add2 + "','"
+ add3 + "','" + city + "','" + state + "','"
+ country + "','" + post +
"','" + phone + "')");
}
stmt.close();
stmt2.close();
rs.close();
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
The PreparedStatement class would have been ideal for this type of operation,
but the mSQL database does not support cursors, and the drivers for Microsoft Access
do not maintain an open connection between transactions. However, the technique that
was used works very well--and works fairly quickly, considering that the transactions
occurred over a modem.
As a reminder, if you are working with multiple databases at the same time, you
must create separate connections and statements for each of the different databases.
Summary
This chapter demonstrates how to use the most common aspects of the JDBC; all
the examples use the JDBC-ODBC bridge or the mSQL-JDBC driver. Most SQL statements
can be processed with all drivers; however, some statements (such as cursor-based
statements) may not work with all drivers or databases. You can usually find a workaround
or use a different JDBC-ODBC driver.
Using the JDBC is simple, as this chapter has shown. By using this Java API, you
can run simple and complex queries and use all forms of database updates. In addition,
the JDBC provides methods and classes that compile statements for later use (the
PreparedStatement class), execute an unknown SQL statement (the execute()
method), process multiple results and updates (the execute() method), and
call procedures with parameters (the CallableStatement class).
Refer to Chapter 19, "The SQL Package," for an overview of the JDBC
API. You may also want to refer to Chapter 42, "Databases and Java," for
an introduction to the JDBC and the beginnings of a handy database utility called
dbutil. Chapter 44, "Building Database Applets with the JDBC,"
provides examples of using the JDBC in applets.
|