JDBC/Oracle Tutorial 1

JDBC 1.0

JDBC (Java Database Connectivity) is a standard API for accessing relational databases from a Java program.  This interface makes it easy to access a database because it provides an abstract layer that hides the low-level details, such as managing sockets.  It also provides for interoperability and portability since it allows a single application to access multiple database management systems simultaneously.  For example, a single application can query and manipulate a database in Oracle and a database in DB2.  Communication with a database management system (DBMS) is through method calls.  These calls are passed to the driver, which in turn, translates them into DBMS-specific calls.  The driver basically acts like a set of library routines.  Therefore, to get your program to communicate with a particular DBMS, you need a compatible JDBC driver.  The basic steps to get your program up and running are:
 
    1. Load the driver and register it with the driver manager
    2. Connect to a database
    3. Create a statement
    4. Execute a query and retrieve the results, or make changes to the database
    5. Disconnect from the database


Steps 1 and 2 are the only DBMS-specific steps.  The rest is DBMS independent with one exception:  the mappings between the DBMS and Java datatypes is somewhat DBMS-specific.  However, this is not a major issue because the driver usually handles the datatype conversions.  Therefore, to make your program work with another DBMS instead of Oracle, you usually only have to change the code associated with steps 1 and 2.  As you can see, JDBC is very powerful and flexible.

This tutorial will cover only JDBC 1.0, which is part of the JDK 1.1x release.  Some of the features of JDBC 2.0, which is part of the JDK 1.2x release, will be covered in the next tutorial. 
 

Getting Started

The first thing you need to do is set the CLASSPATH environment variable so that Java can find the classes for the driver.  Add the following line to your .bashrc file:
 
export CLASSPATH=.:$ORACLE_HOME/jdbc/lib/classes12.zip


You must put this line after  export ORACLE_HOME= /home/o/oracle  (which you should have already added to your .bashrc file).  If CLASSPATH is already defined in that file, add the following line instead of the one above:
 

export CLASSPATH=.:$ORACLE_HOME/jdbc/lib/classes12.zip:$CLASSPATH


You must add this line after the line where CLASSPATH is first defined.

Type source ~/.bashrc for changes to take place without having to close the current Unix shell.
 

Loading and Registering Drivers

This tutorial will show you how to load the Oracle JDBC thin driver.  This driver is a Type 4 driver.  Type 4 drivers are portable because they are written completely in Java.  They are also ideal for applets because they do not require the client to have an Oracle installation.  For a description of other driver types, click here.

Here is the code that loads the driver and registers it with the JDBC driver manager:
 

DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());


Here is another way of doing the above:
 

Class.forName("oracle.jdbc.driver.OracleDriver");


If there are errors loading or registering the driver, the first method throws an SQLException and the second throws a ClassNotFoundException.  If you are unfamiliar with exceptions, click here to learn what they are and how to handle them.

The purpose of a driver manager is to provide a unified interface to a set of drivers (recall that JDBC allows for simultaneous access to multiple database management systems).  It acts as a "facade" to the drivers, which are themselves interfaces, by ensuring that the correct driver function is called.  You will discover that no other functions in this tutorial other than those for loading the driver and connecting to a database include the name of the driver or DBMS as an argument.  The driver manager automatically handles the mappings from JDBC functions to driver functions.
 

Connecting to a Database

The DriverManager class provides the static getConnection() method for opening a database connection.  Below is the method description for getConnection():
 
public static Connection getConnection(String url, String userid, String password) throws SQLException


The url is the DBMS-specific part.  For the Oracle thin driver, it is of the form:  "jdbc:oracle:thin:@host_name:port_number:sid", where host_name is the host name of the database server, port_number is the port number on which a "listener" is listening for connection requests, and sid is the system identifier that identifies the database server.  The url that we are using is "jdbc:oracle:thin:@dbhost.ugrad.cs.ubc.ca:1522:ug", so our connection code is
 

Connection con = DriverManager.getConnection(
  "jdbc:oracle:thin:@dbhost.ugrad.cs.ubc.ca:1522:ug", "username", "password");


The Connection object returned by getConnection() represents one connection to a particular database.  If you want to connect to another database, you will need to create another Connection object using getConnection() with the appropriate url argument (consult the driver's documentation for the format of the url).  If the database is on another DBMS, then you will also need to load and register the appropriate driver.

To disconnect from a database, use the Connection object's close() method.

 

Creating and Executing Statements

A Statement object represents an SQL statement.  It is created using the Connection object's createStatement() method.
 
// con is a Connection object
Statement stmt = con.createStatement();


The SQL statement string is not specified until the statement is executed.

 

Executing Inserts, Updates, and Deletes

To execute a data definition language statement (e.g., create, alter, drop) or a data manipulation language statement (e.g., insert, update, delete), use the executeUpdate() method of the Statement object.  You usually don't define data definition language statements in a Java program.  For insert, update, and delete statements, this method returns the number of rows processed.  Here is an example:
 
// stmt is a statement object
int rowCount = stmt.executeUpdate("INSERT INTO branch VALUES (20, 'Richmond Main', " +
                                  "'18122 No.5 Road', 'Richmond', 5252738)");


Notes:  Do not terminate SQL statements with a semicolon.  You can reuse Statement objects to execute another statement.  To indicate string nesting, alternate between the use of double and single quotation marks.

 

Executing Queries

To execute a query, use the executeQuery() method.  Here is an example:
 
stmt.executeQuery("SELECT branch_id, branch_name FROM branch " +
                  "WHERE branch_city = 'Vancouver'");


The executeQuery() method returns a ResultSet object, which maintains a cursor.  excuteQuery() never returns null.  Cursors were invented to satisfy both the SQL and host programming languages.  SQL queries handle sets of rows at a time, while Java can handle only one row at a time.  The ResultSet class makes it easy to move from row to row and to retrieve the data in the current row (the current row is the row at which the cursor is currently pointing).  Initially, the cursor points before the first row.  The next() method is used to move the cursor to the next row and make it the current row.  The first call to next() moves the cursor to the first row. next() returns false when there are no more rows.  The getXXX() methods are used to fetch column values of Java type XXX from the current row (you will learn more about the getXXX() methods in the "Converting between Java and Oracle Datatypes" section).  For specifying the column, these methods accept a column name or a column number.  Column names are not case sensitive, and column numbers start at 1 (column numbers refer to the columns in the result set).  For a list of all the getXXX() methods, refer to the Java 2 API documentation for the ResultSet class.

Here is an example of using a ResultSet object to retrieve the results of a query:
 

int branchID;
String branchName;
String branchAddr;
String branchCity;
int branchPhone;
. . .
// con is a Connection object
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM branch");
while(rs.next())
{
  branchID = rs.getInt(1);
  branchName = rs.getString("branch_name");
  branchAddr = rs.getString(3);
  branchCity = rs.getString("branch_city");
  branchPhone = rs.getInt(5);
  . . .
}

 

Checking for Null Return Values

Although the above code snippet did not check for null return values, you should always check for nulls for all nullable columns.  If you don't, you may encounter exceptions at runtime.  The ResultSet class provides the method wasNull() for detecting fetched null values.  It returns true if the last value fetched by getXXX() is null.

There are a few important things to consider when checking for nulls.  The SQL NULL value is mapped to Java's null.  However, only object types can represent null; primitive types, such as int and float, cannot.  These types represent null as 0.  Thus when NULL is fetched, getByte(), getShort(), getInt(), getLong(), getFloat(), and getDouble() return 0 instead of null.  Moreover, the getBoolean() method returns false if NULL is fetched.  What if the value stored in the database is actually 0 or false?  To avoid this problem, use the wasNull() method to check for null values.

You cannot insert null using Statement objects.  You have to use PreparedStatement objects instead (see "Using Prepared Statements" and "Inserting Null Values").

 

Closing Statements

After you are done with a Statement object, you can free up memory by using the close() method to close the statement.  When you close a query statement, the corresponding ResultSet object is closed automatically.  A ResultSet can be closed explicitly by calling its close() method.

 

Converting between Java and Oracle Datatypes

Generally, Oracle datatypes and host language datatypes are not the same.  When values are passed from Oracle to Java and vice versa, they need to be cast from one datatype to the other.  The JDBC driver can automatically convert values of Oracle datatypes to values of some of the Java datatypes.  The "Using Prepared Statements" section will cover conversions in the other direction.

The table below shows the getXXX() methods that can be used for some common Oracle datatypes.  An * denotes that the getXXX() method is the preferred one for retrieving values of the given Oracle datatype.  An x denotes that the getXXX() method can be used for retrieving values of the given Oracle datatype.  Use your own judgement when deciding on which getXXX() method to use for NUMBER.  For example, if only integers are stored in a NUMBER column, use getInt().  For the DATE datatype, if a DATE column only stores times, use getTime().  If a DATE column only stores dates, use getDate(). If the column stores both dates and times, use getTimestamp().  Note that getString() can be used for all the Oracle datatypes; however, use getString() only when you really want to receive a string.
 
 
Table 1getXXX() Methods
 
H
A
R

A
R
C
H
A
R
2

O
N
G

U
M
B
E
R

N
T
E
G
E
R

L
O
A
T

A
T
E

A
W

O
N
G

R
A
W

getByte() x x x x x x      
getShort() x x x x x x      
getInt() x x x x * x      
getLong() x x x x x x      
getFloat() x x x x x x      
getDouble() x x x x x *      
getBigDecimal() x x x x x x      
getBoolean() x x x x x x      
getString() * * x x x x x x x
getBytes()               * x
getDate() x x x       x    
getTime() x x x       x    
getTimestamp() x x x       x    
getAsciiStream() x x *         x x
getUnicodeStream() x x *         x x
getBinaryStream()               x *
getObject() x x x x x x x x x

Source:  Hamilton, Graham, and Rick Cattell.  Passing parameters and receiving resultsJDBC: A Java SQL API.  10 June 2001.
 

Note:  You must import java.math.*; if you want to use the BigDecimal class.
 

Using Prepared Statements

A PreparedStatement represents a precompiled SQL statement that contains placeholders to be substituted later with actual values.  Being precompiled means that a prepared statement is compiled at creation time.  The statement can then be executed and re-executed using different values for each placeholder without needing to be recompiled.  Unlike a prepared statement, an SQL statement represented by a Statement object is compiled every time it is executed.

Because PreparedStatement inherits methods from Statement, you can use executeQuery() and executeUpdate() to execute a prepared statement; however, these methods are redefined to have no parameters as you will soon see.  You can also use the close() method to close a prepared statement, and the wasNull() method to check for fetched null values.

Similar to a Statement object, a PreparedStatement is created using the Connection object returned by getConnection().  However, unlike a Statement object, the SQL statement is specified when the prepared statement is created and not when it is executed.  Here's an example of creating a prepared statement:
 

// con is a Connection object created by getConnection()
// note that there is no 'd' in "prepare" in prepareStatement()
PreparedStatement ps = con.prepareStatement("UPDATE branch SET " +
   "branch_addr = ?, branch_phone = ? WHERE branch_city = 'Vancouver'");


Each placeholder is denoted by a ?.  A ? can only be used to represent a column value.  It cannot be used to represent a database object, such as a table or column name.  To build an SQL statement containing user supplied database object names, you will have to use string routines on the SQL string.  You will see an example of this in the third JDBC tutorial.

The setXXX() methods are used to substitute values for the placeholders.  setXXX() accepts a placeholder index and a value of type XXX.  The first placeholder has an index of 1.  The table below lists the valid setXXX() methods for some of the common Oracle datatypes:
 
 
Table 2setXXX() Methods
Oracle Datatype setXXX()
CHAR 
setString()
VARCHAR2 
setString()
LONG 
setString() 
NUMBER 
setBigDecimal()
setBoolean() 
setByte() 
setShort() 
setInt() 
setLong() 
setFloat() 
setDouble()
INTEGER setInt()
FLOAT setDouble() 
RAW
setBytes()
LONGRAW 
setBytes() 
DATE 
setDate() 
setTime() 
setTimestamp() 

 

Unlike getXXX(), the setXXX() methods do not perform any datatype conversions.  You must use a Java value whose type is mapped to the target Oracle datatype.  Therefore, to input a Java value that is not compatible with the target Oracle datatype, you must convert it to a compatible Java type.  The setObject() method can be used to convert a Java value to the format of a JDBC SQL type.  JDBC SQL types are constants that are used to represent generic SQL types; they are not actual Java types.  Like Java types, JDBC SQL types are also mapped to Oracle datatypes.  For information on setObject() see the Java 2 API documentation.  The table below shows the mappings among Oracle, JDBC, and Java types:
 
 
Table 3.  Datatype Mappings
Oracle Datatype JDBC Generic SQL Type Standard Java Type
CHAR 
java.sql.Types.CHAR 
java.lang.String 
VARCHAR2 
java.sql.Types.VARCHAR 
java.lang.String 
LONG 
java.sql.Types.LONGVARCHAR 
java.lang.String 
NUMBER 
java.sql.Types.NUMERIC 
java.math.BigDecimal 
NUMBER 
java.sql.Types.DECIMAL 
java.math.BigDecimal 
NUMBER 
java.sql.Types.BIT 
boolean 
NUMBER 
java.sql.Types.TINYINT 
byte 
NUMBER
java.sql.Types.SMALLINT 
short 
NUMBER
java.sql.Types.INTEGER 
int
NUMBER 
java.sql.Types.BIGINT 
long 
NUMBER 
java.sql.Types.REAL 
float 
NUMBER 
java.sql.Types.FLOAT 
double 
NUMBER 
java.sql.Types.DOUBLE 
double 
RAW 
java.sql.Types.BINARY 
byte[] 
RAW 
java.sql.Types.VARBINARY 
byte[] 
LONGRAW 
java.sql.Types.LONGVARBINARY 
byte[] 
DATE 
java.sql.Types.DATE 
java.sql.Date 
DATE 
java.sql.Types.TIME 
java.sql.Time 
DATE 
java.sql.Types.TIMESTAMP 
javal.sql.Timestamp 

SourceDatatype MappingsOracle10g JDBC Developer's Guide and Reference Release 2 (10.2).  March 2010.
 

Here is an example of using a prepared statement:
 

// con is a Connection object created by getConnection()
PreparedStatement ps = con.prepareStatement("INSERT INTO branch " +
            "(branch_id, branch_name, branch_city) VALUES (?, ?, 'Vancouver')");
int bid[5] = {1, 2, 3, 4, 5};
String bname[5] = {"Main", "Westside", "MacDonald", "Mountain Ridge", "Valley Drive"};
for (int i = 0; i < 5; i++)
{
  setInt(1, bid[i]);
  setString(2, bname[i]);
  ps.executeUpdate();
}


Note:  Once the value of a placeholder has been defined using setXXX(), the value will remain in the prepared statement until it is replaced by another value, or when the clearParameters() method gets called.

 

Inserting Null Values

The setNull() method is used to substitute a placeholder with a null value.  setNull() accepts two parameters: the placeholder index and the JDBC SQL type code.  SQL type codes are found in java.sql.Types (see the Java 2 API documentation).  Refer to Table 3 to select a JDBC SQL type that is compatible with the target Oracle datatype. Alternatively, for setXXX() methods that accept an object as an argument, such as setString(), you can use null directly in setXXX().  The program in the "Sample Program" section contains examples of both methods.

 

Transaction Processing

Any changes made to a database are not necessarily made permanent, right away. If they were, a fatal error halfway through the update would leave the database in an inconsistent state (we will learn more about this in class and in the textbook).  For example, when you transfer money from one bank account to another, you do not want the bank to debit one account and not credit the other because of an error (unless the error benefits you).  You want the debit and credit SQL calls to be treated as one atomic unit of work (all or none principle), so either both the debit and credit are canceled if an error occurs, or both the debit and credit are made permanent if the transfer is successful.  Thus you should group your SQL statements into transactions in order to ensure data integrity.

To make changes to the database permanent and thus visible to other users, use the Connection object's commit() method like this:
 

// con is a Connection object
con.commit();


By default, data manipulation language statements, such as insert, delete, and update, issue an automatic commit.  You should disable auto commit mode so that you can group statements into transactions.  To disable auto commit, use the setAutoCommit() method like this:
 

con.setAutoCommit(false);


When you disable auto commit, you must manually issue commit() after each transaction.  However, if you do not issue a commit or rollback for the last transaction and auto commit is disabled, then a commit is issued automatically for you when the connection is closed.  As a general rule, commit often. This is an analogous to the "save often" rule used when editing any file.

To enable auto commit, use setAutoCommit(true).

Note:  Data definition statements, such as create, drop, and alter, issue an automatic commit regardless of whether or not auto commit is off or on.  This means that everything after the last commit or rollback is committed (you'll encounter rollback next).

To undo changes made to the database by the most recently executed transaction, use the Connection object's rollback() method like this:

 
con.rollback();


rollback() is usually used in error handling code.
 

Error Handling

Exceptions

When a database access error occurs, such as a primary key constraint violation, an SQLException object is thrown.  You must place a try{} block around database access functions that can throw an SQLExceptionand a corresponding catch{} block after the try{} block to catch these exceptions.  Alternatively, you can place a throws SQLException clause in the function header.  For example, the registerDriver() method can throw an SQLException, so you must do one of the following:
 
try
{
  DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
}
catch (SQLException ex)
{
  . . .
}
or
public void someFunction() throws SQLException
{
  . . .
  DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
  . . .
}


Note:  If you don't catch or throw SQLExceptions, you won't be able to compile your code.

Within the catch{} block you can obtain information on the SQLException that was just caught by calling its methods.  The getMessage() method returns the error message.  If the error originated in the Oracle server instead of the JDBC driver, then the message is prefixed with ORA-#####, where ###### is a five digit error number.  For information on what a particular error number means, check out the Oracle Error Reference at the Oracle Documentation Library.  The error reference manual describes the cause of the error and suggests a course of action to take in order to solve the problem.  Another useful method is printStackTrace(), which prints the stack trace to the standard error stream so that you can find out which functions were called prior to the error.
 

Warnings

When a database access warning occurs, an SQLWarning object is thrown.  SQLWarning is a subclass of SQLException.  However, unlike regular exceptions, SQLWarnings do not stop the execution of an application; you do not need to place a try{} block around a method that can throw an SQLWarning.  SQLWarnings are actually "silently" attached to the object whose method caused it to be thrown.  If more than one warning occurred, the warnings are chained, one after the other.  The following code retrieves the warnings from a ResultSet object and prints each warning message:
 
// rs is a ResultSet object
SQLWarning wn = rs.getWarnings();
while (wn != null)
{
  System.out.println("Message: " + wn.getMessage());
  // get the next warning
  wn = wn.getNextWarning();
}


SQLWarnings are actually rare.  In fact, the Oracle JDBC drivers generally do not support them.  The most common warning is data truncation.  When a value read is truncated, a DataTrunction warning is reported (DataTruncation is a subclass of SQLWarning).  When a value written is truncated, a DataTruncation exception (not warning) is thrown.  The methods in this class allow you to find out the number of bytes actually transferred and the number of bytes that should have been transferred (see the Java 2 API documentation for the details).
 

Sequences

In most cases, tuples in relations are uniquely identified by numbers.  Branches in our branch relation are uniquely identified by the branch id.  So far, we have been entering these numbers ourselves.  However, Oracle provides a function which can automatically generate unique numbers.  This is done with the following command:
 
CREATE SEQUENCE <sequence_name>


Therefore, to create a sequence called branch_counter, we specify:
 

CREATE SEQUENCE branch_counter


We normally define sequences after we define the relation which uses the sequence, but before we insert any tuples into that relation.  Sequences are normally not created in a Java program.  To start generating sequence numbers, we do the following in our INSERT statements:
 

INSERT
INTO BRANCH (branch_id, branch_name, branch_addr, branch_city,
             branch_phone)
VALUES      (branch_counter.nextval, 'West', '7291 W. 16th',
             'Coquitlam', 5559238)


Every time the NEXTVAL variable is accessed, the sequence number corresponding to branch_counter increases by 1. Therefore, the sequence numbers generated by branch_counter for branch_id are 1, 2, ...  (Note: multiple accesses to NEXTVAL within the same SQL statement result in the same value).

NEXTVAL can be used only in the following cases:

A sequence does not necessarily have to increment by 1 and start at 1. We have the following options:
 
START WITH   <integer>
INCREMENT BY <integer>
MAXVALUE     <integer>
MINVALUE     <integer>
CYCLE | NOCYCLE
ORDER | NOORDER


The semantics of these options should be self-explanatory. To illustrate:
 

CREATE SEQUENCE branch_counter
START WITH    10
INCREMENT BY  2
MAXVALUE      20
CYCLE


results in the sequence:
 

10, 12, 14, 16, 18, 20, 10, 12 ...


Of course, if we use sequences for primary key fields, Oracle will not allow the CYCLE option to be part of the definition of the sequence.

Another useful variable is the CURRVAL variable, which returns the most recently generated value by NEXTVAL.

Here is an example of the use of CURRVAL:
 

SELECT *
FROM   BRANCH
WHERE  branch_id = branch_counter.currval


which selects the most recently generated branch record.

To alter or delete sequences, we use the commands:
 

ALTER SEQUENCE <sequence-name> [<sequence options>]


and
 

DROP SEQUENCE <sequence-name>


respectively.

For example, to alter the branch counter sequence to increment by 100, to a maximum value of 1000:
 

ALTER SEQUENCE branch_counter
INCREMENT BY 100
MAXVALUE 1000


The only option that we cannot alter after a sequence has been created is START WITH. To change the START WITH value, we would have to delete the sequence and create it again with the new value.

To delete the branch_counter sequence:

 
DROP SEQUENCE branch_counter


You can query the settings of your sequences by referencing the SEQ table, which contains fields such as SEQUENCE_NAME, MIN_VALUE, MAX_VALUE, LAST_NUMBER, INCREMENT_BY, and C (for cycle).

Below is an example of how to use a sequence in Java.  A branch tuple is inserted and then returned in the query that follows.
 

// stmt is a Statement object
// branch_counter is a sequence
stmt.executeUpdate("INSERT INTO branch VALUES (branch_counter.nextval, 'West', " +
                   "'7291 W.16th', 'Coquitlam', 5559238)");
ResultSet rs = stmt.executeQuery("SELECT * FROM branch WHERE " +
                                 "branch_id = branch_counter.currval");


Note:  Not all DBMSs support sequences.

 

Miscellaneous Topics

Getting Information about a Result Set

The ResultSetMetaData class provides methods to get information about the columns in a result set.  For example, this class provides methods to return the number of columns, the name of a given column, the maximum character width of a given column, the datatype of a given column, and whether or not null values are permitted in a given column.  Refer to the ResultSetMetaData class in the Java 2 API documentation for the class description.  The following example gets the number of columns in a ResultSet object:
 
// rs is a ResultSet object
ResultSetMetaData rsmd = rs.getMetaData();

int count = rsmd.getColumnCount();

 

Floating Point Numbers

The following lines will not compile because the default type for a floating point number is double:
 
float f = 3.14;

// ps is a PreparedStatement object
ps.setFloat(1, 1234.99);


In order for this to compile, you need to cast the number to the float datatype:
 

float f = (float)3.14;
ps.setFloat(1, (float)1234.99);
To avoid the truncation of large floating point numbers, use getDouble(), setDouble(), and the double datatype.

 

Dates, Times, and Timestamps

JDBC supports dates, times, and timestamps that are only in ISO standard format, which is different from the default date format used by Oracle (the default date format is dd-MMM-yy, e.g. 23-JUN-01).  Consequently, date literals must be in the form {d 'yyyy-MM-dd'}.  For example, the following inserts 23-JUN-01 into table abc:
 
stmt.excuteUpdate("INSERT INTO abc VALUES ({d '2001-06-23'})");


You can find out the meaning of each format symbol by checking out the Java 2 API documentation for the SimpleDateFormat class.  Note that 'M' is for month and 'm' is for minute in hour.

In addition, time literals must be of the form {t 'H:mm:ss'}.  'H' is for hour in day (0-23) and 'h' is for hour in am/pm (1-12).  For example, the following query returns tuples that have inspectionTime equal to 05:12 and 45 seconds:
 

stmt.executeQuery("SELECT xyz FROM abc WHERE inspectionTime = {t '05:12:45'}");


The format for timestamp literals is {ts 'yyyy-MM-dd H:mm:ss.[f...]} e.g., {ts '2001-05-18 08:15:00'}.  The "f...", which represents fractions of a second, is optional (you won't find the symbol 'f' in the documentation for the SimpleDateFormat class).

Everything between the {} braces is mapped by the driver into DBMS-specific syntax.  So on input to Oracle, the driver will convert dates, times, and timestamps in ISO format to their equivalent Oracle representations.  For example, {d, '2001-06-23'} will get converted to "23 JUN 2001", {t, '13:30:45'} will get converted to "13:30:45" assuming the Oracle server is configured to use a 24 hour clock, and {ts '2001-05-18 08:15:00'} will get converted to "MAY 18 2001 08:15:00".

For information on formatting dates, times, and timestamps refer to the Java 2 API documentation for the SimpleDateFormat class and the DateFormat abstract class.  These classes are used to parse and format dates.  Parsing converts a date string to a date object.  Formatting converts a date object to a date string.  The Calendar and GregorianCalendar classes are also worth checking out.  They are used to manipulate and obtain information on date fields (e.g., year, month, day).  You can also check out the Formatting tutorial at Sun.

If you check the Java 2 API documentation, you will notice that there are two classes to represent dates.  One is java.util.Date and the other is java.sql.Date.  A java.util.Date represents the date and time with millisecond precision.  A java.sql.Date is basically a JDBC-compliant java.util.Date with the time component zeroed.

Here are some examples:
 

Example 1.

In this example, the date string "18/08/01" is converted into a valid JDBC date, and then it is inserted into the, initially, empty table "abc".  The date is then fetched and displayed in its original format.  The formatting and parsing methods in the SimpleDateFormat class accept/return java.util.Date, but the setDate() method in the PreparedStatement class and the getDate() method in the ResultSet class accept/return java.sql.Date.  Therefore, you should make sure that you use the correct date type.  The getTime() and setTime() methods of both java.util.Date and java.sql.Date are used to convert between the two dates.
 

import java.sql.*;
import java.util.*;
// for SimpleDateFormat
import java.text.*;
. . .
public static void main(String args[])
              throws SQLException, ParseException
{
  . . .
  // con is a Connection object
  con.setAutoCommit(false);
  String stringDate = new String("18/08/01");
  SimpleDateFormat fm = new SimpleDateFormat("dd/MM/yy");
  // parse() interprets a string according to the
  // SimpleDateFormat's format pattern and then converts
  // the string to a date object
  java.util.Date utilDate = fm.parse(stringDate);
  // The getTime() method returns the the number of
  // milliseconds between January 1, 1970, 00:00:00 GMT
  // and the given date. Dates are represented
  // with millisecond precision.
  // The constructor for java.sql.Date zeroes the time
  // component.
  java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
  PreparedStatement ps = con.prepareStatement(
                          "INSERT INTO abc VALUES (?)");
  ps.setDate(1, sqlDate);
  ps.executeUpdate();
  con.commit();
  Statement stmt = con.createStatement();
  ResultSet rs = stmt.executeQuery("SELECT * FROM abc");
  while(rs.next())
  {
     sqlDate = rs.getDate(1);
 
     // we need to convert the date to java.util.Date
     // so that we can use format()
     utilDate.setTime(sqlDate.getTime());
 
     // convert the fetched JDBC date to the original format 
     // format() returns a string
     System.out.println(fm.format(utilDate));
  }
  . . .
}


Example 2.

This example returns today's date if today's date + 60 days is less than or equal to today's date + 2 months.  The Oracle keyword "sysdate" represents the current date.  The table "dual" is a dummy table provided by Oracle.  Note that you can add and subtract constants to and from dates.  The constants are treated as days.  To add and subtract months and years, use the add() method in the GregorianCalendar class and the constants in the Calendar class.  Note, also, that the standard comparison operators such as <, =, <>, != (<> is the same as !=), <=, etc. can be used with dates.
 

import java.sql.*;
// for GregorianCalendar
import java.util.*;
. . .
// con is a Connection object
PreparedStatement ps = con.prepareStatement("SELECT sysdate " + 
                       "FROM dual where (sysdate + 60) <= ?");
// creates a new calendar initialized to the current date and time
GregorianCalendar gregCalendar = new GregorianCalendar();
// add 2 months to the current date and time
gregCalendar.add(Calendar.MONTH, 2);
// gregCalendar.getTime() returns a java.util.Date
// the second getTime() returns the date in milliseconds
// recall that dates are represented with millisecond precision
java.sql.Date sqlDate = new java.sql.Date(
                        gregCalendar.getTime().getTime());
ps.setDate(1, sqlDate);
 
ResultSet rs = ps.executeQuery();
if (rs.next())
{
  sqlDate = rs.getDate(1);
  System.out.println(sqlDate.toString());
}
. . .


For information about the Oracle date datatype, such as how to use SQL*Plus (not JDBC) to SELECT or INSERT a date with both a date and time component, check out this link (note:  the date format elements listed on that page is not the same as those in the SimpleDateFormat class).  Because the Oracle date datatype includes both a date and time component, the ResultSetMetaData's getColumnType() method returns Types.TIMESTAMP instead of Types.DATE .   You will encounter getColumnType() in the sample program in tutorial 3.

 

Formatting Numbers

If you want to find out how to format numbers, check the Java 2 API documentation on the DecimalFormat class and the NumberFormat abstract class.  You can also check out the Formatting tutorial at Sun.

Here is an example:

This example will fetch numbers from column 'a'  (Oracle type float) in table "xyz", multiply each number by 1.07, and then format them to two decimal places.
 

import java.sql.*;
// for NumberFormat
import java.text.*;
. . .
// con is a Connection object
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a FROM xyz");
// getNumberInstance() returns a general purpose number formatter
NumberFormat numFormatter = NumberFormat.getNumberInstance();
double num;
while (rs.next())
{
  num = rs.getDouble(1);
  num *= 1.07;
  // Set the maximum and minimum number of digits to 2.
  // If necessary, rounding will be performed and
  // zeros will be added. 
  numFormatter.setMinimumFractionDigits(2);
  numFormatter.setMaximumFractionDigits(2);
  // format() formats the number and returns it as a string
  System.out.println(numFormatter.format(num));
}
. . .


To place a '$' in front of each number, replace NumberFormat.getNumberInstance() with NumberFormat.getCurrencyInstance(Locale.CANADA).  You will need to import java.util.* to use the Locale class.

 

Using '_' and '%' in LIKE Clauses

The special characters '_' and '%' are used in LIKE clauses to represent exactly one character, and zero or more characters respectively.  Because they have special meaning, to treat these characters literally as normal characters you need to place an escape character immediately before '_' or '%'.  For example, to match branch names that contain an underscore you could do something like this:
 
// stmt is a Statement object
stmt.executeQuery("SELECT branch_name FROM branch WHERE branch_name " +
                  "LIKE '%&_%' {ESCAPE '&'}");


The {ESCAPE '&'} is used to indicate to the Oracle JDBC driver that & is the escape character.  You should choose an escape character that is not part of the search string.

Note:  If you want to use the backslash character (\) as an escape character, you must enter it twice (that is, \\).  For example:
 

stmt.executeQuery("SELECT branch_name FROM branch WHERE branch_name " +
                  "LIKE '%\\_%' {ESCAPE '\\'}");


This method of specifying special characters in LIKE clauses is specific to Oracle.  Don't confuse this with how special characters are specified in a Java string literal (the \ character is used as the escape character).

 

Apostrophes

To store an apostrophe (') in an SQL string, use two apostrophes ('').  For example,
 
// con is a Connection object
Statement stmt = con.createStatement();
stmt.executeUpdate("INSERT INTO xyz VALUES ('Richard''s car')");


will insert Richard's car into table xyz.  For some reason, you cannot use the string escape character \ to specify the apostrophe.  It is likely that the string is sent directly to Oracle without being processed first by Java because apostrophes are, in fact, specified in Oracle using ''.

However, for the PreparedStatement method setString(), you need to use the string escape character \ in order to use an apostrophe.  You cannot use '' because the string is processed by Java before it is sent to Oracle (this probably has something to do with how a prepared statement is handled).  For example,

 
PreparedStatement ps = con.prepareStatement("INSERT INTO xyz VALUES (?)");
ps.setString(1,"Richard\'s car");
ps.executeUpdate();


will insert Richard's car into table xyz.

For other special characters such as ", you need to use the \ escape character.  For example,

 
Statement stmt = con.createStatement();
stmt.executeUpdate("INSERT INTO xyz VALUES ('\"ABC\"')");


will insert "ABC" into table xyz.

 

Sample Program

Click here to download the code.  You will also need to download hb15.zip as well.  Do not unzip it.  hb15.zip is a package that contains routines for formatted output.  The reason we need this package is because the standard Java 2 API does not provide, for a text-based interface, any specific library routines for formatting output into straight columns.  The only way that this can be achieved easily is through the use of a GUI.  You can find documentation, a tutorial, and examples on how to use this package at the author's site.  This program uses only Format.printf() and Parameters.add().  The comments in the sample program should be sufficient for you to understand how those methods are used.

You will need to include hb15.zip in the CLASSPATH environment variable in order to compile the program.  For example, if hb15.zip is in the same directory as the program code, the CLASSPATH variable in your .bashrc file may look like this (on the UNIX platform, path entries are separated using ":"):
 

export CLASSPATH=.:./hb15.zip:/home/o/oracle/jdbc/lib/classes12.zip


Play around with the program, and study the code.  The program uses Swing (a GUI package) to draw a login window for entering your Oracle username and password.  It is a bad idea to hard code your password in a program because someone could read the source to obtain your password.  It is also a bad idea to read the password from the command line.  This is because you cannot turn off echoing in Java.  For example, don't use code like this:
 

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String password = in.readLine();


The  optional JDBC/Swing tutorial will introduce you to Swing by examining a Swing-based JDBC program.
 

Note:

You may find that you have a problem with connecting to Oracle if you are using a wireless connection, try moving to a wired connection.

Working from home or wireless

If you are working from home, you likely have already discovered that you will get an error message like "Cannot establish a network adapter". To solve this problem, you will need to use Xmanager/putty (or other SSH tool of your choice) to create an SSH tunnel to dbhost.ugrad.cs via remote.ugrad.
To do this complete the following steps:
  1. Set up Xmanager as per the instructions located http://www.cs.ubc.ca/support/available-software-xmanager
  2. Add the SSH tunnel by going to the connection "Properties" window: Connection->SSH->Tunneling.
    Create a local outgoing connection on port 1522.
    Destination host: dbhost.ugrad.cs.ubc.ca port 1522
    The Putty equivalent can be found at http://oldsite.precedence.co.uk/nc/putty.html
    For Unix based systems you can use the following command in the Terminal:
    ssh -l username -L localhost:1522:dbhost.ugrad.cs.ubc.ca:1522 remote.ugrad.cs.ubc.ca
  3. Disconnect and reconnect from the remote.ugrad.cs.ubc.ca server if currently connected.
    On reconnect, your firewall software may prompt for access
  4. The connection string for your java program will now change to
    jdbc:oracle:thin:@localhost:1522:ug
Please note that your Xmanager/SSH connection must remain open for Eclipse or any other app to be able to make use of that connection string.
If you still continue to have problems, check that you haven't installed a personal version of Oracle which can cause problems with these instructions

Structuring Transactions

This section briefly discusses two ways that transactions could be structured in a program.  The first way represents related transactions as methods in a class.  The second way represents each transaction as a class.  The first way is sufficient for transactions that are relatively short and only access a single table, such as those in the sample program above.  The sample program in the JDBC/Swing Tutorial also uses this method.  However, there are a few problems with this method.  For one thing, it is not easy to group transactions that access different and/or multiple tables.  Should a transaction that access both the branch and driver tables be placed in the branch class or the driver class?  Another problem is that existing classes may have to be updated to accommodate new transactions.  Can you think of any more pros and cons of this method?

The second method is a more object-orientated solution.  It decouples objects that use the transaction from the details of the transaction itself.  The details of each transaction is encapsulated and hidden.  Existing classes do not have to be modified when you add a new transaction.  For example, you could declare an abstract class or interface called Transaction that contains the method execute() whose implementation is empty.  You could then define concrete transaction classes that implement this method.  To execute a transaction, you would simply call the transaction's execute() method.  Parameters for the transaction could be passed in when the transaction is constructed.  Another benefit is that this method supports undo, redo, and logging.  For this to work, a transaction would have to store the current state before executing the transaction.  For multiple undos, you could construct a history list of transactions.

 

Links


JDBC/Oracle Tutorial 1
Last updated May 8 2011