Java developers no longer need to explicitly load JDBC drivers using
code like
Class.forName()
to register a JDBC driver. The DriverManager
class takes care of this by automatically locating a suitable driver when the DriverManager.getConnection()
method is called. This feature is backward-compatible, so no changes are needed
to the existing JDBC code.
JDBC 4.0 also provides an improved developer experience by minimizing
the boiler-plate code we need to write in Java applications that access
relational databases. It also provides utility classes to improve the JDBC
driver registration and unload mechanisms as well as managing data sources and
connection objects.
With JDBC 4.0, Java developers can now specify SQL queries using
Annotations
,
taking the advantage of metadata support available with the release of Java SE
5.0 (Tiger). Annotation-based SQL queries allow
us to specify the SQL query string right within the Java code using an Annotation
keyword. This way we don't have to look in two different files for JDBC code
and the database query it's calling. For example, if you have a method called getActiveLoans()
to get a list of the active loans in a loan processing database, you can
decorate it with a @Query(sql="SELECT * FROM
LoanApplicationDetails WHERE LoanStatus = 'A'")
annotation.
Also, the final version of the Java SE 6 development kit (JDK 6)--as
opposed to the runtime environment (JRE 6)--will have a database based on Apache Derby
bundled with it. This will help developers explore the new JDBC features
without having to download, install, and configure a database product
separately.
The major features added in JDBC 4.0 include:
1. Auto-loading of JDBC driver class
2. Connection management enhancements
3. Support for
RowId
SQL type
4.
DataSet
implementation of SQL using Annotation
s
5. SQL exception handling enhancements
6. SQL XML support
There are also other features such as improved support for large objects
(BLOB/CLOB) and National Character Set Support. These features are examined in
detail in the following section.
Auto-Loading of JDBC Driver
In JDBC 4.0, we no longer need to explicitly load JDBC drivers using
Class.forName()
.
When the method getConnection
is called, the DriverManager
will attempt to locate a suitable driver from among the JDBC drivers that were
loaded at initialization and those loaded explicitly using the same class
loader as the current application.
The
DriverManager
methods getConnection
and getDrivers
have been enhanced to support the Java SE Service Provider mechanism (SPM).
According to SPM, a service is defined as a well-known set of interfaces and
abstract classes, and a service provider is a specific implementation of a
service. It also specifies that the service provider configuration files are
stored in the META-INF/services
directory. JDBC 4.0 drivers must include
the file META-INF/services/java.sql.Driver
. This file contains the
name of the JDBC driver's implementation of java.sql.Driver
.
For example, to load the JDBC driver to connect to a Apache Derby database, the
META-INF/services/java.sql.Driver
file would contain the
following entry:org.apache.derby.jdbc.EmbeddedDriver
Let's take a quick look at how we can use this new feature to load a
JDBC driver manager. The following listing shows the sample code that we
typically use to load the JDBC driver. Let's assume that we need to connect to
an Apache Derby database, since we will be using this in the sample application
explained later in the article:
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
Connection conn =
DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
But in JDBC 4.0, we don't need the
Class.forName()
line. We can simply
call getConnection()
to get the database connection.
Note that this is for getting a database connection in stand-alone mode.
If you are using some type of database connection pool to manage connections,
then the code would be different.
Connection Management
Prior to JDBC 4.0, we relied on the JDBC URL to define a data source
connection. Now with JDBC 4.0, we can get a connection to any data source by
simply supplying a set of parameters (such as host name and port number) to a
standard connection factory mechanism. New methods were added to
Connection
and Statement
interfaces to permit improved connection state
tracking and greater flexibility when managing Statement
objects in pool environments. The metadata facility (JSR-175)
is used to manage the active connections. We can also get metadata information,
such as the state of active connections, and can specify a connection as
standard (Connection
, in the case of stand-alone applications),
pooled (PooledConnection
), or even as a distributed connection (XAConnection
)
for XA transactions. Note that we don't use the XAConnection
interface directly. It's used by the transaction manager inside a Java EE
application server such as WebLogic, WebSphere, or JBoss.RowId Support
The
RowID
interface was added to JDBC 4.0 to support the ROWID
data type which is supported by databases such as Oracle and DB2. RowId
is useful in cases where there are multiple records that don't have a unique
identifier column and you need to store the query output in a Collection
(such Hashtable
) that doesn't allow duplicates. We can use ResultSet
's
getRowId()
method to get a RowId
and PreparedStatement
's
setRowId()
method to use the RowId
in a query.
An important thing to remember about the
RowId
object
is that its value is not portable between data sources and should be considered
as specific to the data source when using the set
or update
methods in PreparedStatement
and ResultSet
respectively. So, it
shouldn't be shared between different Connection
and ResultSet
objects.
The method
getRowIdLifetime()
in DatabaseMetaData
can be used to determine the lifetime validity of the RowId
object. The return value or row id can have one of the values listed in below
table
RowId Value
|
Description
|
ROWID_UNSUPPORTED
|
Doesn't support
ROWID data type. |
ROWID_VALID_OTHER
|
Lifetime of the
RowID is dependent on database vendor implementation. |
ROWID_VALID_TRANSACTION
|
Lifetime of the
RowID is within the current transaction as long as the
row in the database table is not deleted. |
ROWID_VALID_SESSION
|
Lifetime of the
RowID is the duration of the current session as long as
the row in the database table is not deleted. |
ROWID_VALID_FOREVER
|
Lifetime of the
RowID is unlimited as long as the row in the database
table is not deleted. |
Annotation-Based SQL Queries
The JDBC 4.0 specification leverages annotations (added in Java SE 5) to
allow developers to associate a SQL query with a Java class without writing a
lot of code to achieve this association. Also, by using the Generics (JSR
014) and metadata (JSR 175) APIs, we can associate the SQL
queries with Java objects specifying query input and output parameters. We can
also bind the query results to Java classes to speed the processing of query
output. We don't need to write all the code we usually write to populate the
query result into a Java object. There are two main annotations when specifying
SQL queries in Java code:
Select
and Update
.Select Annotation
The
Select
annotation is used to specify a select query in a
Java class for the get
method to retrieve data from a
database table. Table 2 shows various attributes of the Select
annotation and their uses.
Name
|
Type
|
Description
|
sql
|
String
|
SQL Select query string.
|
value
|
String
|
Same as
sql
attribute. |
tableName
|
String
|
Name of the database table against which the
sql will be invoked. |
readOnly, connected, scrollable
|
Boolean
|
Flags used to indicate if the returned
DataSet is read-only or updateable,
is connected to the back-end database, and is scrollable when used in connected mode respectively. |
allColumnsMapped
|
Boolean
|
Flag to indicate if the column names in the
sql annotation element are mapped
1-to-1 with the fields in the DataSet. |
Here's an example of
Select
annotation to get all the
active loans from the loan database:interface LoanAppDetailsQuery extends BaseQuery {
@Select("SELECT * FROM LoanDetais where LoanStatus = 'A'")
DataSet<LoanApplication> getAllActiveLoans();
}
The
sql
annotation allows I/O parameters as well (a parameter
marker is represented with a question mark followed by an integer). Here's an
example of a parameterized sql
query.interface LoanAppDetailsQuery extends BaseQuery {
@Select(sql="SELECT * from LoanDetails
where borrowerFirstName= ?1 and borrowerLastName= ?2")
DataSet<LoanApplication> getLoanDetailsByBorrowerName(String borrFirstName,
String borrLastName);
}
Update Annotation
The Update annotation is used to decorate a Query interface
method to update one or more records in a database table. An Update annotation must include a sql annotation type
element. Here's an example of Update annotation:
interface LoanAppDetailsQuery extends BaseQuery {
@Update(sql="update LoanDetails set LoanStatus = ?1
where loanId = ?2")
boolean updateLoanStatus(String loanStatus, int loanId);
}
SQL Exception Handling Enhancements
Exception handling is an important part of Java programming, especially
when connecting to or running a query against a back-end relational database.
SQLException
is the class that we have been using to indicate database related errors. JDBC 4.0
has several enhancements in SQLException
handling. The following
are some of the enhancements made in JDBC 4.0 release to provide a better
developer's experience when dealing with SQLException
s:- New
SQLException
sub-classes - Support for causal relationships
- Support for enhanced for-each loop
New SQLException classes
The new subclasses of SQLException were created to
provide a means for Java programmers to write more portable error-handling
code. There are two new categories of SQLException
introduced in JDBC 4.0:
- SQL non-transient exception
- SQL transient exception
Non-Transient
Exception: This exception is thrown when a retry of the same
JDBC operation would fail unless the cause of the SQLException is corrected. Table 3 shows the new exception classes that are added in
JDBC 4.0 as subclasses of SQLNonTransientException (SQLState class values are defined in SQL 2003 specification.):
Exception class
|
SQLState value
|
SQLFeatureNotSupportedException |
0A
|
SQLNonTransientConnectionException |
08
|
SQLDataException |
22
|
SQLIntegrityConstraintViolationException |
23
|
SQLInvalidAuthorizationException |
28
|
SQLSyntaxErrorException |
42
|
Transient
Exception: This exception is thrown when a previously failed
JDBC operation might be able to succeed when the operation is retried without
any intervention by application-level functionality. The new exceptions
extending SQLTransientException are listed in Table 4.
Exception class
|
SQLState value
|
SQLTransientConnectionException |
08
|
SQLTransactionRollbackException |
40
|
SQLTimeoutException |
None
|
Causal Relationships
The SQLException class now supports the Java SE chained exception mechanism (also known
as the Cause facility), which gives us the ability to handle multiple SQLExceptions (if the back-end database supports a multiple exceptions feature)
thrown in a JDBC operation. This scenario occurs when executing a statement
that may throw more than one SQLException .
We can use getNextException() method in SQLException to iterate through the exception chain. Here's some sample code to process
SQLException causal relationships:
catch(SQLException ex) {
while(ex != null) {
LOG.error("SQL State :" + ex.getSQLState());
LOG.error("Error Code:" + ex.getErrorCode());
LOG.error("Message:" + ex.getMessage());
Throwable t = ex.getCause();
while(t != null) {
LOG.error("Cause:" + t);
t = t.getCause();
}
ex = ex.getNextException();
}
}
Enhanced For-Each Loop
The SQLException class implements the Iterable interface,
providing support for the for-each loop feature added in Java SE 5. The
navigation of the loop will walk through SQLException and its
cause. Here's a code snippet showing the enhanced for-each loop feature added
in SQLException.
catch(SQLException ex) {
for(Throwable e : ex ) {
LOG.error("Error occurred: " + e);
}
}
Support for National Character Set Conversion
Following is the list of new enhancements made in JDBC classes when
handling the National Character Set:
- JDBC data
types: New JDBC data types, such as
NCHAR
,NVARCHAR
,LONGNVARCHAR
, andNCLOB
were added. PreparedStatement
: New methodssetNString
,setNCharacterStream
, andsetNClob
were added.CallableStatement
: New methodsgetNClob
,getNString
, andgetNCharacterStream
were added.ResultSet
: New methodsupdateNClob
,updateNString
, andupdateNCharacterStream
were added toResultSet
interface.
Enhanced Support for Large Objects (BLOBs and CLOBs)
The following is the list of enhancements made in JDBC 4.0 for handling the LOBs:Connection
: New methods (createBlob()
,createClob()
, andcreateNClob()
) were added to create new instances ofBLOB
,CLOB
, andNCLOB
objects.PreparedStatement
: New methodssetBlob()
,setClob()
, andsetNClob()
were added to insert aBLOB
object using anInputStream
object, and to insertCLOB
andNCLOB
objects using aReader
object.- LOBs: There
is a new method (
free()
) added inBlob
,Clob
, andNClob
interfaces to release the resources that these objects hold.
Now, let's look at some of the new classes added to the java.sql and javax.jdbc packages and what services they provide.
JDBC 4.0 API: New Classes
RowId (java.sql)
As described earlier, this interface is a representation of an SQL ROWID value in the database. ROWID is a built-in SQL data type
that is used to identify a specific data row in a database table. ROWID is often used in queries that return rows from a table where the output
rows don't have an unique ID column.
Methods in CallableStatement, PreparedStatement, and ResultSet interfaces such as getRowId and setRowId allow a programmer to access a SQL ROWID value.
The RowId interface also provides a method (called getBytes()) to return the value of ROWID as a byte
array. DatabaseMetaData interface has a new method called getRowIdLifetime that can be used to determine the lifetime of a RowId object. A RowId's scope can be one of three types:
- Duration of
the database transaction in which the
RowId
was created - Duration of
the session in which the
RowId
was created - The identified row in the database table, as long as it is not deleted
DataSet (java.sql)
The DataSet interface provides a type-safe
view of the data returned from executing of a SQL Query. DataSet can operate in a connected or disconnected mode. It is similar to ResultSet in its functionality when used in connected mode. A DataSet, in a disconnected mode, functions similar to a CachedRowSet. Since DataSet extends List interface, we can iterate through the rows returned from a query.
There are also
several new methods added in the existing classes such as Connection
(createSQLXML, isValid) and ResultSet (getRowId).