In this lesson you will learn the basics of the JDBC API.
-
Getting Started sets up a basic database development environment and shows you how to compile and run the JDBC tutorial samples.
-
Processing SQL Statements with JDBC outlines the steps required to process any SQL statement. The pages that follow describe these steps in more detail:
-
Establishing a Connection connects you to your database.
-
Connecting with DataSource Objects shows you how to connect to your database with
DataSourceobjects, the preferred way of getting a connection to a data source.
-
Handling SQLExceptions shows you how to handle exceptions caused by database errors.
-
Setting Up Tables
describes all the database tables used in the JDBC tutorial samples and
how to create and populate tables with JDBC API and SQL scripts.
-
Retrieving and Modifying Values from Result Sets develop the process of configuring your database, sending queries, and retrieving data from your database.
-
Using Prepared Statements describes a more flexible way to create database queries.
-
Using Transactions shows you how to control when a database query is actually executed.
-
Establishing a Connection connects you to your database.
-
Using RowSet Objects introduces you to
RowSetobjects; these are objects that hold tabular data in a way that make it more flexible and easier to use than result sets. The pages that follow describe the different kinds ofRowSetobjects available:
-
Using Advanced Data Types introduces you to other data types; the pages that follow describe these data types in further detail:
-
Using Stored Procedures
shows you how to create and use a stored procedure, which is a group of
SQL statements that can be called like a Java method with variable
input and output parameters.
-
Using JDBC with GUI API demonstrates how to integrate JDBC with the Swing API.
Getting Started
The sample code that comes with this tutorial creates a database that
is used by a proprietor of a small coffee house called The Coffee
Break, where coffee beans are sold by the pound and brewed coffee is
sold by the cup.
The following steps configure a JDBC development environment with which you can compile and run the tutorial samples:
Ensure that the full directory path of the Java SE SDK
Note that if you are using another DBMS, you might have to alter the code of the tutorial samples.
Contact the vendor of your database to obtain a JDBC driver for your DBMS.
There are many possible implementations of JDBC drivers. These implementations are categorized as follows:
Installing a JDBC driver generally consists of copying the driver to your computer, then adding the location of it to your class path. In addition, many JDBC drivers other than Type 4 drivers require you to install a client-side API. No other special configuration is usually needed.
Ensure that the Apache Ant executable file is in your
Modify these XML files as follows:
Similarly, if you are using MySQL, your
Note: For simplicity in demonstrating the JDBC API,
the JDBC tutorial sample code does not perform the password management
techniques that a deployed system normally uses. In a production
environment, you can follow the Oracle Database password management
guidelines and disable any sample accounts. See the section Securing Passwords in Application Design in Managing Security for Application Developers in Oracle Database Security Guide for password management guidelines and other security recommendations.
Note: No corresponding Ant target exists in the
If you are using either Java DB or MySQL, then from the same directory, run the following command to delete existing sample database tables, recreate the tables, and populate them. For Java DB, this command also creates the database if it does not already exist:
Note: You should run the command
For example, to run the class
In general, to process any SQL statement with JDBC, you follow these steps:
For example,
There are three different kinds of statements:
See
Retrieving and Modifying Values from Result Sets for more information.
For example,
See
Retrieving and Modifying Values from Result Sets for more information.
For example, the method
JDBC throws an
In JDBC 4.1, which is available in Java SE release 7 and later, you can use a try-with-resources statement to automatically close
The following statement is an
First, you need to establish a connection with the data source you
want to use. A data source can be a DBMS, a legacy file system, or some
other source of data with a corresponding JDBC driver. Typically, a JDBC
application connects to a target data source using one of two classes:
This page covers the following topics:
The method
Note:
The following steps configure a JDBC development environment with which you can compile and run the tutorial samples:
- Install the latest version of the Java SE SDK on your computer
- Install your database management system (DBMS) if needed
- Install a JDBC driver from the vendor of your database
- Install Apache Ant
- Install Apache Xalan
- Download the sample code
- Modify the
build.xmlfile - Modify the tutorial properties file
- Compile and package the samples
- Create databases, tables, and populate tables
- Run the samples
Install the latest version of the Java SE SDK on your computer
Install the latest version of the Java SE SDK on your computer.Ensure that the full directory path of the Java SE SDK
bin directory is in your PATH environment variable so that you can run the Java compiler and the Java application launcher from any directory.Install your database management system (DBMS) if needed
You may use Java DB, which comes with the latest version of Java SE SDK. This tutorial has been tested for the following DBMS:Note that if you are using another DBMS, you might have to alter the code of the tutorial samples.
Install a JDBC driver from the vendor of your database
If you are using Java DB, it already comes with a JDBC driver. If you are using MySQL, install the latest version of Connector/J.Contact the vendor of your database to obtain a JDBC driver for your DBMS.
There are many possible implementations of JDBC drivers. These implementations are categorized as follows:
-
Type 1: Drivers that implement the JDBC API as a
mapping to another data access API, such as ODBC (Open Database
Connectivity). Drivers of this type are generally dependent on a native
library, which limits their portability. The JDBC-ODBC Bridge is an
example of a Type 1 driver.
Note: The JDBC-ODBC Bridge should be considered a transitional solution. It is not supported by Oracle. Consider using this only if your DBMS does not offer a Java-only JDBC driver.
-
Type 2: Drivers that are written partly in the Java
programming language and partly in native code. These drivers use a
native client library specific to the data source to which they connect.
Again, because of the native code, their portability is limited.
Oracle's OCI (Oracle Call Interface) client-side driver is an example of
a Type 2 driver.
-
Type 3: Drivers that use a pure Java client and
communicate with a middleware server using a database-independent
protocol. The middleware server then communicates the client's requests
to the data source.
-
Type 4: Drivers that are pure Java and implement the
network protocol for a specific data source. The client connects
directly to the data source.
Installing a JDBC driver generally consists of copying the driver to your computer, then adding the location of it to your class path. In addition, many JDBC drivers other than Type 4 drivers require you to install a client-side API. No other special configuration is usually needed.
Install Apache Ant
These steps use Apache Ant, a Java-based tool, to build, compile, and run the JDBC tutorial samples. Go to the following link to download Apache Ant:http://ant.apache.org/Ensure that the Apache Ant executable file is in your
PATH environment variable so that you can run it from any directory.Install Apache Xalan
The sampleRSSFeedsTable.java, which is described in Using SQLXML Objects, requires Apache Xalan if your DBMS is Java DB. The sample uses Apache Xalan-Java. Go to the following link to download it:http://xml.apache.org/xalan-j/Download the sample code
The sample code,JDBCTutorial.zip, consists of the following files:propertiesjavadb-build-properties.xmljavadb-sample-properties.xmlmysql-build-properties.xmlmysql-sample-properties.xml
sqljavadbcreate-procedures.sqlcreate-tables.sqldrop-tables.sqlpopulate-tables.sql
mysqlcreate-procedures.sqlcreate-tables.sqldrop-tables.sqlpopulate-tables.sql
src/com/oracle/tutorial/jdbcCachedRowSetSample.javaCityFilter.javaClobSample.javaCoffeesFrame.javaCoffeesTable.javaCoffeesTableModel.javaDatalinkSample.javaExampleRowSetListener.javaFilteredRowSetSample.javaJdbcRowSetSample.javaJDBCTutorialUtilities.javaJoinSample.javaProductInformationTable.javaRSSFeedsTable.javaStateFilter.javaStoredProcedureJavaDBSample.javaStoredProcedureMySQLSample.javaSuppliersTable.javaWebRowSetSample.java
txtcolombian-description.txt
xmlrss-coffee-industry-news.xmlrss-the-coffee-break-blog.xml
build.xml
<JDBC tutorial directory>. Unzip the contents of JDBCTutorial.zip into <JDBC tutorial directory>.Modify the build.xml file
Thebuild.xml file is the build file that Apache Ant uses to compile and execute the JDBC samples. The files properties/javadb-build-properties.xml and properties/mysql-build-properties.xml contain additional Apache Ant properties required for Java DB and MySQL, respectively. The files properties/javadb-sample-properties.xml and properties/mysql-sample-properties.xml contain properties required by the sample.Modify these XML files as follows:
Modify build.xml
In thebuild.xml file, modify the property ANTPROPERTIES to refer to either properties/javadb-build-properties.xml or properties/mysql-build-properties.xml, depending on your DBMS. For example, if you are using Java DB, your build.xml file would contain this:<property
name="ANTPROPERTIES"
value="properties/javadb-build-properties.xml"/>
<import file="${ANTPROPERTIES}"/>
build.xml file would contain this:<property
name="ANTPROPERTIES"
value="properties/mysql-build-properties.xml"/>
<import file="${ANTPROPERTIES}"/>
Modify database-specific properties file
In theproperties/javadb-build-properties.xml or properties/mysql-build-properties.xml file (depending on your DBMS), modify the following properties, as described in the following table:| Property | Description |
|---|---|
JAVAC |
The full path name of your Java compiler, javac |
JAVA |
The full path name of your Java runtime executable, java |
PROPERTIESFILE |
The name of the properties file, either properties/javadb-sample-properties.xml or properties/mysql-sample-properties.xml |
MYSQLDRIVER |
The full path name of your MySQL driver. For Connector/J, this is typically <Connector/J installation directory>/mysql-connector-java-version-number.jar. |
JAVADBDRIVER |
The full path name of your Java DB driver. This is typically <Java DB installation directory>/lib/derby.jar. |
XALANDIRECTORY |
The full path name of the directory that contains Apache Xalan. |
CLASSPATH |
The class path that the JDBC tutorial uses. You do not need to change this value. |
XALAN |
The full path name of the file xalan.jar. |
DB.VENDOR |
A value of either derby or mysql
depending on whether you are using Java DB or MySQL, respectively. The
tutorial uses this value to construct the URL required to connect to the
DBMS and identify DBMS-specific code and SQL statements. |
DB.DRIVER |
The fully qualified class name of the JDBC driver. For Java DB, this is org.apache.derby.jdbc.EmbeddedDriver. For MySQL, this is com.mysql.jdbc.Driver. |
DB.HOST |
The host name of the computer hosting your DBMS. |
DB.PORT |
The port number of the computer hosting your DBMS. |
DB.SID |
The name of the database the tutorial creates and uses. |
DB.URL.NEWDATABASE |
The connection URL used to connect to your DBMS when creating a new database. You do not need to change this value. |
DB.URL |
The connection URL used to connect to your DBMS. You do not need to change this value. |
DB.USER |
The name of the user that has access to create databases in the DBMS. |
DB.PASSWORD |
The password of the user specified in DB.USER. |
DB.DELIMITER |
The character used to separate SQL statements. Do not change this value. It should be the semicolon character (;). |
Modify the tutorial properties file
The tutorial samples use the values in either theproperties/javadb-sample-properties.xml file or properties/mysql-sample-properties.xml file (depending on your DBMS) to connect to the DBMS and initialize databases and tables, as described in the following table:| Property | Description |
|---|---|
dbms |
A value of either derby or mysql
depending on whether you are using Java DB or MySQL, respectively. The
tutorial uses this value to construct the URL required to connect to the
DBMS and identify DBMS-specific code and SQL statements. |
jar_file |
The full path name of the JAR file that contains all the class files of this tutorial. |
driver |
The fully qualified class name of the JDBC driver. For Java DB, this is org.apache.derby.jdbc.EmbeddedDriver. For MySQL, this is com.mysql.jdbc.Driver. |
database_name |
The name of the database the tutorial creates and uses. |
user_name |
The name of the user that has access to create databases in the DBMS. |
password |
The password of the user specified in user_name. |
server_name |
The host name of the computer hosting your DBMS. |
port_number |
The port number of the computer hosting your DBMS. |
Compile and package the samples
At a command prompt, change the current directory to<JDBC tutorial directory>. From this directory, run the following command to compile the samples and package them in a jar file:ant jar
Create databases, tables, and populate tables
If you are using MySQL, then run the following command to create a database:ant create-mysql-database
build.xml
file that creates a database for Java DB. The database URL for Java DB,
which is used to establish a database connection, includes the option
to create the database (if it does not already exist). See
Establishing a Connection for more information.If you are using either Java DB or MySQL, then from the same directory, run the following command to delete existing sample database tables, recreate the tables, and populate them. For Java DB, this command also creates the database if it does not already exist:
ant setup
ant setup
every time before you run one of the Java classes in the sample. Many
of these samples expect specific data in the contents of the sample's
database tables.Run the samples
Each target in thebuild.xml file corresponds to a Java class or SQL script in the JDBC samples. The following table lists the targets in the build.xml file, which class or script each target executes, and other classes or files each target requires:| Ant Target | Class or SQL Script | Other Required Classes or Files |
|---|---|---|
javadb-create-procedure |
javadb/create-procedures.sql; see the build.xml file to view other SQL statements that are run |
No other required files |
mysql-create-procedure |
mysql/create-procedures.sql. |
No other required files |
run |
JDBCTutorialUtilities |
No other required classes |
runct |
CoffeesTable |
JDBCTutorialUtilities |
runst |
SuppliersTable |
JDBCTutorialUtilities |
runjrs |
JdbcRowSetSample |
JDBCTutorialUtilities |
runcrs |
CachedRowSetSample, ExampleRowSetListener |
JDBCTutorialUtilities |
runjoin |
JoinSample |
JDBCTutorialUtilities |
runfrs |
FilteredRowSetSample |
JDBCTutorialUtilities, CityFilter, StateFilter |
runwrs |
WebRowSetSample |
JDBCTutorialUtilities |
runclob |
ClobSample |
JDBCTutorialUtilities, txt/colombian-description.txt |
runrss |
RSSFeedsTable |
JDBCTutorialUtilities, the XML files contained in the xml directory |
rundl |
DatalinkSample |
JDBCTutorialUtilities |
runspjavadb |
StoredProcedureJavaDBSample |
JDBCTutorialUtilities, SuppliersTable, CoffeesTable |
runspmysql |
StoredProcedureMySQLSample |
JDBCTutorialUtilities, SuppliersTable, CoffeesTable |
runframe |
CoffeesFrame |
JDBCTutorialUtilities, CoffeesTableModel |
CoffeesTable, change the current directory to <JDBC tutorial directory>, and from this directory, run the following command:ant runct
Processing SQL Statements with JDBC
- Establishing a connection.
- Create a statement.
- Execute the query.
- Process the
ResultSetobject. - Close the connection.
CoffeesTables.viewTable, from the tutorial sample to demonstrate these steps. This method outputs the contents of the table COFFEES. This method will be discussed in more detail later in this tutorial:public static void viewTable(Connection con, String dbName)
throws SQLException {
Statement stmt = null;
String query = "select COF_NAME, SUP_ID, PRICE, " +
"SALES, TOTAL " +
"from " + dbName + ".COFFEES";
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + "\t" + supplierID +
"\t" + price + "\t" + sales +
"\t" + total);
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
if (stmt != null) { stmt.close(); }
}
}
Establishing Connections
First, establish a connection with the data source you want to use. A data source can be a DBMS, a legacy file system, or some other source of data with a corresponding JDBC driver. This connection is represented by aConnection object. See
Establishing a Connection for more information.Creating Statements
AStatement is an interface that represents a SQL statement. You execute Statement objects, and they generate ResultSet objects, which is a table of data representing a database result set. You need a Connection object to create a Statement object.For example,
CoffeesTables.viewTable creates a Statement object with the following code:stmt = con.createStatement();
Statement: Used to implement simple SQL statements with no parameters.PreparedStatement: (ExtendsStatement.) Used for precompiling SQL statements that might contain input parameters. See Using Prepared Statements for more information.CallableStatement:(ExtendsPreparedStatement.) Used to execute stored procedures that may contain both input and output parameters. See Stored Procedures for more information.
Executing Queries
To execute a query, call anexecute method from Statement such as the following:execute: Returnstrueif the first object that the query returns is aResultSetobject. Use this method if the query could return one or moreResultSetobjects. Retrieve theResultSetobjects returned from the query by repeatedly callingStatement.getResultSet.executeQuery: Returns oneResultSetobject.executeUpdate: Returns an integer representing the number of rows affected by the SQL statement. Use this method if you are usingINSERT,DELETE, orUPDATESQL statements.
CoffeesTables.viewTable executed a Statement object with the following code:ResultSet rs = stmt.executeQuery(query);
Processing ResultSet Objects
You access the data in aResultSet object through a
cursor. Note that this cursor is not a database cursor. This cursor is a
pointer that points to one row of data in the ResultSet object. Initially, the cursor is positioned before the first row. You call various methods defined in the ResultSet object to move the cursor.For example,
CoffeesTables.viewTable repeatedly calls the method ResultSet.next to move the cursor forward by one row. Every time it calls next, the method outputs the data in the row where the cursor is currently positioned:try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + "\t" + supplierID +
"\t" + price + "\t" + sales +
"\t" + total);
}
}
// ...
Closing Connections
When you are finished using aStatement, call the method Statement.close to immediately release the resources it is using. When you call this method, its ResultSet objects are closed.For example, the method
CoffeesTables.viewTable ensures that the Statement object is closed at the end of the method, regardless of any SQLException objects thrown, by wrapping it in a finally block:} finally {
if (stmt != null) { stmt.close(); }
}
SQLException when it encounters an error during an interaction with a data source. See
Handling SQL Exceptions for more information.In JDBC 4.1, which is available in Java SE release 7 and later, you can use a try-with-resources statement to automatically close
Connection, Statement, and ResultSet objects, regardless of whether an SQLException has been thrown. An automatic resource statement consists of a try statement and one or more declared resources. For example, you can modify CoffeesTables.viewTable so that its Statement object closes automatically, as follows:public static void viewTable(Connection con) throws SQLException {
String query = "select COF_NAME, SUP_ID, PRICE, " +
"SALES, TOTAL " +
"from COFFEES";
try (Statement stmt = con.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + ", " + supplierID +
", " + price + ", " + sales +
", " + total);
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
try-with-resources statement, which declares one resource, stmt, that will be automatically closed when the try block terminates:try (Statement stmt = con.createStatement()) {
// ...
}
Establishing a Connection
-
DriverManager: This fully implemented class connects an application to a data source, which is specified by a database URL. When this class first attempts to establish a connection, it automatically loads any JDBC 4.0 drivers found within the class path. Note that your application must manually load any JDBC drivers prior to version 4.0.
-
DataSource: This interface is preferred overDriverManagerbecause it allows details about the underlying data source to be transparent to your application. ADataSourceobject's properties are set so that it represents a particular data source. See Connecting with DataSource Objects for more information. For more information about developing applications with theDataSourceclass, see the latest The Java EE Tutorial.
DriverManager class instead of the DataSource class because it is easier to use and the samples do not require the features of the DataSource class.This page covers the following topics:
Using the DriverManager Class
Connecting to your DBMS with theDriverManager class involves calling the method DriverManager.getConnection. The following method, JDBCTutorialUtilities.getConnection, establishes a database connection:public Connection getConnection() throws SQLException {
Connection conn = null;
Properties connectionProps = new Properties();
connectionProps.put("user", this.userName);
connectionProps.put("password", this.password);
if (this.dbms.equals("mysql")) {
conn = DriverManager.getConnection(
"jdbc:" + this.dbms + "://" +
this.serverName +
":" + this.portNumber + "/",
connectionProps);
} else if (this.dbms.equals("derby")) {
conn = DriverManager.getConnection(
"jdbc:" + this.dbms + ":" +
this.dbName +
";create=true",
connectionProps);
}
System.out.println("Connected to database");
return conn;
}
DriverManager.getConnection establishes a
database connection. This method requires a database URL, which varies
depending on your DBMS. The following are some examples of database
URLs:-
MySQL:
jdbc:mysql://localhost:3306/, wherelocalhostis the name of the server hosting your database, and3306is the port number
-
Java DB:
jdbc:derby:testdb;create=true, wheretestdbis the name of the database to connect to, andcreate=trueinstructs the DBMS to create the database.
Note: This URL establishes a database connection with the Java DB Embedded Driver. Java DB also includes a Network Client Driver, which uses a different URL.
Properties object.Note:
-
Typically, in the database URL, you also specify the name of an
existing database to which you want to connect. For example, the URL
jdbc:mysql://localhost:3306/mysqlrepresents the database URL for the MySQL database namedmysql. The samples in this tutorial use a URL that does not specify a specific database because the samples create a new database.
-
In previous versions of JDBC, to obtain a connection, you first had to initialize your JDBC driver by calling the method
Class.forName. This methods required an object of typejava.sql.Driver. Each JDBC driver contains one or more classes that implements the interfacejava.sql.Driver. The drivers for Java DB areorg.apache.derby.jdbc.EmbeddedDriverandorg.apache.derby.jdbc.ClientDriver, and the one for MySQL Connector/J iscom.mysql.jdbc.Driver. See the documentation of your DBMS driver to obtain the name of the class that implements the interfacejava.sql.Driver.
Any JDBC 4.0 drivers that are found in your class path are automatically loaded. (However, you must manually load any drivers prior to JDBC 4.0 with the methodClass.forName.)
Connection object, which represents a connection with the DBMS or a specific database. Query the database through this object.Specifying Database Connection URLs
A database connection URL is a string that your DBMS JDBC driver uses to connect to a database. It can contain information such as where to search for the database, the name of the database to connect to, and configuration properties. The exact syntax of a database connection URL is specified by your DBMS.Java DB Database Connection URLs
The following is the database connection URL syntax for Java DB:jdbc:derby:[subsubprotocol:][databaseName]
[;attribute=value]*
subsubprotocolspecifies where Java DB should search for the database, either in a directory, in memory, in a class path, or in a JAR file. It is typically omitted.databaseNameis the name of the database to connect to.attribute=valuerepresents an optional, semicolon-separated list of attributes. These attributes enable you to instruct Java DB to perform various tasks, including the following:- Create the database specified in the connection URL.
- Encrypt the database specified in the connection URL.
- Specify directories to store logging and trace information.
- Specify a user name and password to connect to the database.
MySQL Connector/J Database URL
The following is the database connection URL syntax for MySQL Connector/J:jdbc:mysql://[host][,failoverhost...]
[:port]/[database]
[?propertyName1][=propertyValue1]
[&propertyName2][=propertyValue2]...
host:portis the host name and port number of the computer hosting your database. If not specified, the default values ofhostandportare 127.0.0.1 and 3306, respectively.databaseis the name of the database to connect to. If not specified, a connection is made with no default database.failoveris the name of a standby database (MySQL Connector/J supports failover).propertyName=propertyValuerepresents an optional, ampersand-separated list of properties. These attributes enable you to instruct MySQL Connector/J to perform various tasks.Connecting with DataSource Objects
This section coversDataSourceobjects, which are the preferred means of getting a connection to a data source. In addition to their other advantages, which will be explained later,DataSourceobjects can provide connection pooling and distributed transactions. This functionality is essential for enterprise database computing. In particular, it is integral to Enterprise JavaBeans (EJB) technology.
This section shows you how to get a connection using theDataSourceinterface and how to use distributed transactions and connection pooling. Both of these involve very few code changes in your JDBC application.
The work performed to deploy the classes that make these operations possible, which a system administrator usually does with a tool (such as Apache Tomcat or Oracle WebLogic Server), varies with the type ofDataSourceobject that is being deployed. As a result, most of this section is devoted to showing how a system administrator sets up the environment so that programmers can use aDataSourceobject to get connections.
The following topics are covered:
- Using DataSource Objects to Get Connections
- Deploying Basic DataSource Objects
- Deploying Other DataSource Implementations
- Getting and Using Pooled Connections
- Deploying Distributed Transactions
- Using Connections for Distributed Transactions
Using DataSource Objects to Get a Connection
In Establishing a Connection, you learned how to get a connection using theDriverManagerclass. This section shows you how to use aDataSourceobject to get a connection to your data source, which is the preferred way.
Objects instantiated by classes that implement theDataSourcerepresent a particular DBMS or some other data source, such as a file. ADataSourceobject represents a particular DBMS or some other data source, such as a file. If a company uses more than one data source, it will deploy a separateDataSourceobject for each of them. TheDataSourceinterface is implemented by a driver vendor. It can be implemented in three different ways:
- A basic
DataSourceimplementation produces standardConnectionobjects that are not pooled or used in a distributed transaction. - A
DataSourceimplementation that supports connection pooling producesConnectionobjects that participate in connection pooling, that is, connections that can be recycled. - A
DataSourceimplementation that supports distributed transactions producesConnectionobjects that can be used in a distributed transaction, that is, a transaction that accesses two or more DBMS servers.
DataSourceimplementation. For example, the Java DB JDBC driver includes the implementationorg.apache.derby.jdbc.ClientDataSourceand for MySQL,com.mysql.jdbc.jdbc2.optional.MysqlDataSource. If your client runs on Java 8 compact profile 2, then the Java DB JDBC driver isorg.apache.derby.jdbc.BasicClientDataSource40. The sample for this tutorial requires compact profile 3 or greater.
ADataSourceclass that supports distributed transactions typically also implements support for connection pooling. For example, aDataSourceclass provided by an EJB vendor almost always supports both connection pooling and distributed transactions.
Suppose that the owner of the thriving chain of The Coffee Break shops, from the previous examples, has decided to expand further by selling coffee over the Internet. With the large amount of online business expected, the owner will definitely need connection pooling. Opening and closing connections involves a great deal of overhead, and the owner anticipates that this online ordering system will necessitate a sizable number of queries and updates. With connection pooling, a pool of connections can be used over and over again, avoiding the expense of creating a new connection for every database access. In addition, the owner now has a second DBMS that contains data for the recently acquired coffee roasting company. This means that the owner will want to be able to write distributed transactions that use both the old DBMS server and the new one.
The chain owner has reconfigured the computer system to serve the new, larger customer base. The owner has purchased the most recent JDBC driver and an EJB application server that works with it to be able to use distributed transactions and get the increased performance that comes with connection pooling. Many JDBC drivers are available that are compatible with the recently purchased EJB server. The owner now has a three-tier architecture, with a new EJB application server and JDBC driver in the middle tier and the two DBMS servers as the third tier. Client computers making requests are the first tier.
Deploying Basic DataSource Objects
The system administrator needs to deployDataSourceobjects so that The Coffee Break's programming team can start using them. Deploying aDataSourceobject consists of three tasks:
- Creating an instance of the
DataSourceclass - Setting its properties
- Registering it with a naming service that uses the Java Naming and Directory Interface (JNDI) API
DataSourceinterface, that is, one that does not support connection pooling or distributed transactions. In this case there is only oneDataSourceobject that needs to be deployed. A basic implementation ofDataSourceproduces the same kind of connections that theDriverManagerclass produces.
Creating Instance of DataSource Class and Setting its Properties
Suppose a company that wants only a basic implementation ofDataSourcehas bought a driver from the JDBC vendor DB Access, Inc. This driver includes the classcom.dbaccess.BasicDataSourcethat implements theDataSourceinterface. The following code excerpt creates an instance of the classBasicDataSourceand sets its properties. After the instance ofBasicDataSourceis deployed, a programmer can call the methodDataSource.getConnectionto get a connection to the company's database,CUSTOMER_ACCOUNTS. First, the system administrator creates theBasicDataSourceobjectdsusing the default constructor. The system administrator then sets three properties. Note that the following code is typically be executed by a deployment tool:
The variablecom.dbaccess.BasicDataSource ds = new com.dbaccess.BasicDataSource(); ds.setServerName("grinder"); ds.setDatabaseName("CUSTOMER_ACCOUNTS"); ds.setDescription("Customer accounts database for billing");dsnow represents the databaseCUSTOMER_ACCOUNTSinstalled on the server. Any connection produced by theBasicDataSourceobjectdswill be a connection to the databaseCUSTOMER_ACCOUNTS.
Registering DataSource Object with Naming Service That Uses JNDI API
With the properties set, the system administrator can register theBasicDataSourceobject with a JNDI (Java Naming and Directory Interface) naming service. The particular naming service that is used is usually determined by a system property, which is not shown here. The following code excerpt registers theBasicDataSourceobject and binds it with the logical namejdbc/billingDB:
This code uses the JNDI API. The first line creates anContext ctx = new InitialContext(); ctx.bind("jdbc/billingDB", ds);InitialContextobject, which serves as the starting point for a name, similar to root directory in a file system. The second line associates, or binds, theBasicDataSourceobjectdsto the logical namejdbc/billingDB. In the next code excerpt, you give the naming service this logical name, and it returns theBasicDataSourceobject. The logical name can be any string. In this case, the company decided to use the namebillingDBas the logical name for theCUSTOMER_ACCOUNTSdatabase.
In the previous example,jdbcis a subcontext under the initial context, just as a directory under the root directory is a subdirectory. The namejdbc/billingDBis like a path name, where the last item in the path is analogous to a file name. In this case,billingDBis the logical name that is given to theBasicDataSourceobjectds. The subcontextjdbcis reserved for logical names to be bound toDataSourceobjects, sojdbcwill always be the first part of a logical name for a data source.
Using Deployed DataSource Object
After a basicDataSourceimplementation is deployed by a system administrator, it is ready for a programmer to use. This means that a programmer can give the logical data source name that was bound to an instance of aDataSourceclass, and the JNDI naming service will return an instance of thatDataSourceclass. The methodgetConnectioncan then be called on thatDataSourceobject to get a connection to the data source it represents. For example, a programmer might write the following two lines of code to get aDataSourceobject that produces a connection to the databaseCUSTOMER_ACCOUNTS.
The first line of code gets an initial context as the starting point for retrieving aContext ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("jdbc/billingDB");DataSourceobject. When you supply the logical namejdbc/billingDBto the methodlookup, the method returns theDataSourceobject that the system administrator bound tojdbc/billingDBat deployment time. Because the return value of the methodlookupis a JavaObject, we must cast it to the more specificDataSourcetype before assigning it to the variableds.
The variabledsis an instance of the classcom.dbaccess.BasicDataSourcethat implements theDataSourceinterface. Calling the methodds.getConnectionproduces a connection to theCUSTOMER_ACCOUNTSdatabase.
TheConnection con = ds.getConnection("fernanda","brewed");getConnectionmethod requires only the user name and password because the variabledshas the rest of the information necessary for establishing a connection with theCUSTOMER_ACCOUNTSdatabase, such as the database name and location, in its properties.
Advantages of DataSource Objects
Because of its properties, aDataSourceobject is a better alternative than theDriverManagerclass for getting a connection. Programmers no longer have to hard code the driver name or JDBC URL in their applications, which makes them more portable. Also,DataSourceproperties make maintaining code much simpler. If there is a change, the system administrator can update data source properties and not be concerned about changing every application that makes a connection to the data source. For example, if the data source were moved to a different server, all the system administrator would have to do is set theserverNameproperty to the new server name.
Aside from portability and ease of maintenance, using aDataSourceobject to get connections can offer other advantages. When theDataSourceinterface is implemented to work with aConnectionPoolDataSourceimplementation, all of the connections produced by instances of thatDataSourceclass will automatically be pooled connections. Similarly, when theDataSourceimplementation is implemented to work with anXADataSourceclass, all of the connections it produces will automatically be connections that can be used in a distributed transaction. The next section shows how to deploy these types ofDataSourceimplementations.
Deploying Other DataSource Implementations
A system administrator or another person working in that capacity can deploy aDataSourceobject so that the connections it produces are pooled connections. To do this, he or she first deploys aConnectionPoolDataSourceobject and then deploys aDataSourceobject implemented to work with it. The properties of theConnectionPoolDataSourceobject are set so that it represents the data source to which connections will be produced. After theConnectionPoolDataSourceobject has been registered with a JNDI naming service, theDataSourceobject is deployed. Generally only two properties must be set for theDataSourceobject:descriptionanddataSourceName. The value given to thedataSourceNameproperty is the logical name identifying theConnectionPoolDataSourceobject previously deployed, which is the object containing the properties needed to make the connection.
With theConnectionPoolDataSourceandDataSourceobjects deployed, you can call the methodDataSource.getConnectionon theDataSourceobject and get a pooled connection. This connection will be to the data source specified in theConnectionPoolDataSourceobject's properties.
The following example describes how a system administrator for The Coffee Break would deploy aDataSourceobject implemented to provide pooled connections. The system administrator would typically use a deployment tool, so the code fragments shown in this section are the code that a deployment tool would execute.
To get better performance, The Coffee Break company has bought a JDBC driver from DB Access, Inc. that includes the classcom.dbaccess.ConnectionPoolDS, which implements theConnectionPoolDataSourceinterface. The system administrator creates create an instance of this class, sets its properties, and registers it with a JNDI naming service. The Coffee Break has bought itsDataSourceclass,com.applogic.PooledDataSource, from its EJB server vendor, Application Logic, Inc. The classcom.applogic.PooledDataSourceimplements connection pooling by using the underlying support provided by theConnectionPoolDataSourceclasscom.dbaccess.ConnectionPoolDS.
TheConnectionPoolDataSourceobject must be deployed first. The following code creates an instance ofcom.dbaccess.ConnectionPoolDSand sets its properties:
After thecom.dbaccess.ConnectionPoolDS cpds = new com.dbaccess.ConnectionPoolDS(); cpds.setServerName("creamer"); cpds.setDatabaseName("COFFEEBREAK"); cpds.setPortNumber(9040); cpds.setDescription("Connection pooling for " + "COFFEEBREAK DBMS");ConnectionPoolDataSourceobject has been deployed, the system administrator deploys theDataSourceobject. The following code registers thecom.dbaccess.ConnectionPoolDSobjectcpdswith a JNDI naming service. Note that the logical name being associated with thecpdsvariable has the subcontextpooladded under the subcontextjdbc, which is similar to adding a subdirectory to another subdirectory in a hierarchical file system. The logical name of any instance of the classcom.dbaccess.ConnectionPoolDSwill always begin withjdbc/pool. Oracle recommends putting allConnectionPoolDataSourceobjects under the subcontextjdbc/pool:
Next, theContext ctx = new InitialContext(); ctx.bind("jdbc/pool/fastCoffeeDB", cpds);DataSourceclass that is implemented to interact with thecpdsvariable and other instances of thecom.dbaccess.ConnectionPoolDSclass is deployed. The following code creates an instance of this class and sets its properties. Note that only two properties are set for this instance ofcom.applogic.PooledDataSource. Thedescriptionproperty is set because it is always required. The other property that is set,dataSourceName, gives the logical JNDI name forcpds, which is an instance of thecom.dbaccess.ConnectionPoolDSclass. In other words,cpdsrepresents theConnectionPoolDataSourceobject that will implement connection pooling for theDataSourceobject.
The following code, which would probably be executed by a deployment tool, creates aPooledDataSourceobject, sets its properties, and binds it to the logical namejdbc/fastCoffeeDB:
At this point, acom.applogic.PooledDataSource ds = new com.applogic.PooledDataSource(); ds.setDescription("produces pooled connections to COFFEEBREAK"); ds.setDataSourceName("jdbc/pool/fastCoffeeDB"); Context ctx = new InitialContext(); ctx.bind("jdbc/fastCoffeeDB", ds);DataSourceobject is deployed from which an application can get pooled connections to the databaseCOFFEEBREAK.
Getting and Using Pooled Connections
A connection pool is a cache of database connection objects. The objects represent physical database connections that can be used by an application to connect to a database. At run time, the application requests a connection from the pool. If the pool contains a connection that can satisfy the request, it returns the connection to the application. If no connections are found, a new connection is created and returned to the application. The application uses the connection to perform some work on the database and then returns the object back to the pool. The connection is then available for the next connection request.
Connection pools promote the reuse of connection objects and reduce the number of times that connection objects are created. Connection pools significantly improve performance for database-intensive applications because creating connection objects is costly both in terms of time and resources.
Now that theseDataSourceandConnectionPoolDataSourceobjects are deployed, a programmer can use theDataSourceobject to get a pooled connection. The code for getting a pooled connection is just like the code for getting a nonpooled connection, as shown in the following two lines:
The variablectx = new InitialContext(); ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");dsrepresents aDataSourceobject that produces pooled connections to the databaseCOFFEEBREAK. You need to retrieve thisDataSourceobject only once because you can use it to produce as many pooled connections as needed. Calling the methodgetConnectionon thedsvariable automatically produces a pooled connection because theDataSourceobject that thedsvariable represents was configured to produce pooled connections.
Connection pooling is generally transparent to the programmer. There are only two things you need to do when you are using pooled connections:
-
Use a
DataSourceobject rather than theDriverManagerclass to get a connection. In the following line of code,dsis aDataSourceobject implemented and deployed so that it will create pooled connections andusernameandpasswordare variables that represent the credentials of the user that has access to the database:
Connection con = ds.getConnection(username, password);
-
Use a
finallystatement to close a pooled connection. The followingfinallyblock would appear after thetry/catchblock that applies to the code in which the pooled connection was used:
try { Connection con = ds.getConnection(username, password); // ... code to use the pooled // connection con } catch (Exception ex { // ... code to handle exceptions } finally { if (con != null) con.close(); }
The following sample code gets aDataSourceobject that produces connections to the databaseCOFFEEBREAKand uses it to update a price in the tableCOFFEES:
The connection in this code sample participates in connection pooling because the following are true:import java.sql.*; import javax.sql.*; import javax.ejb.*; import javax.naming.*; public class ConnectionPoolingBean implements SessionBean { // ... public void ejbCreate() throws CreateException { ctx = new InitialContext(); ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB"); } public void updatePrice(float price, String cofName, String username, String password) throws SQLException{ Connection con; PreparedStatement pstmt; try { con = ds.getConnection(username, password); con.setAutoCommit(false); pstmt = con.prepareStatement("UPDATE COFFEES " + "SET PRICE = ? " + "WHERE COF_NAME = ?"); pstmt.setFloat(1, price); pstmt.setString(2, cofName); pstmt.executeUpdate(); con.commit(); pstmt.close(); } finally { if (con != null) con.close(); } } private DataSource ds = null; private Context ctx = null; }
- An instance of a class implementing
ConnectionPoolDataSourcehas been deployed. - An instance of a class implementing
DataSourcehas been deployed, and the value set for itsdataSourceNameproperty is the logical name that was bound to the previously deployedConnectionPoolDataSourceobject.
-
It imports the
javax.sql,javax.ejb, andjavax.namingpackages in addition tojava.sql.
TheDataSourceandConnectionPoolDataSourceinterfaces are in thejavax.sqlpackage, and the JNDI constructorInitialContextand methodContext.lookupare part of thejavax.namingpackage. This particular example code is in the form of an EJB component that uses API from thejavax.ejbpackage. The purpose of this example is to show that you use a pooled connection the same way you use a nonpooled connection, so you need not worry about understanding the EJB API.
-
It uses a
DataSourceobject to get a connection instead of using theDriverManagerfacility.
-
It uses a
finallyblock to ensure that the connection is closed.
ConnectionPoolDataSourceobject and aDataSourceobject properly, an application uses thatDataSourceobject to get a pooled connection. An application should, however, use afinallyblock to close the pooled connection. For simplicity, the preceding example used afinallyblock but nocatchblock. If an exception is thrown by a method in thetryblock, it will be thrown by default, and thefinallyclause will be executed in any case.
Deploying Distributed Transactions
DataSourceobjects can be deployed to get connections that can be used in distributed transactions. As with connection pooling, two different class instances must be deployed: anXADataSourceobject and aDataSourceobject that is implemented to work with it.
Suppose that the EJB server that The Coffee Break entrepreneur bought includes theDataSourceclasscom.applogic.TransactionalDS, which works with anXADataSourceclass such ascom.dbaccess.XATransactionalDS. The fact that it works with anyXADataSourceclass makes the EJB server portable across JDBC drivers. When theDataSourceandXADataSourceobjects are deployed, the connections produced will be able to participate in distributed transactions. In this case, the classcom.applogic.TransactionalDSis implemented so that the connections produced are also pooled connections, which will usually be the case forDataSourceclasses provided as part of an EJB server implementation.
TheXADataSourceobject must be deployed first. The following code creates an instance ofcom.dbaccess.XATransactionalDSand sets its properties:
The following code registers thecom.dbaccess.XATransactionalDS xads = new com.dbaccess.XATransactionalDS(); xads.setServerName("creamer"); xads.setDatabaseName("COFFEEBREAK"); xads.setPortNumber(9040); xads.setDescription("Distributed transactions for COFFEEBREAK DBMS");com.dbaccess.XATransactionalDSobjectxadswith a JNDI naming service. Note that the logical name being associated withxadshas the subcontextxaadded underjdbc. Oracle recommends that the logical name of any instance of the classcom.dbaccess.XATransactionalDSalways begin withjdbc/xa.
Next, theContext ctx = new InitialContext(); ctx.bind("jdbc/xa/distCoffeeDB", xads);DataSourceobject that is implemented to interact withxadsand otherXADataSourceobjects is deployed. Note that theDataSourceclass,com.applogic.TransactionalDS, can work with anXADataSourceclass from any JDBC driver vendor. Deploying theDataSourceobject involves creating an instance of thecom.applogic.TransactionalDSclass and setting its properties. ThedataSourceNameproperty is set tojdbc/xa/distCoffeeDB, the logical name associated withcom.dbaccess.XATransactionalDS. This is theXADataSourceclass that implements the distributed transaction capability for theDataSourceclass. The following code deploys an instance of theDataSourceclass:
Now that instances of the classescom.applogic.TransactionalDS ds = new com.applogic.TransactionalDS(); ds.setDescription("Produces distributed transaction " + "connections to COFFEEBREAK"); ds.setDataSourceName("jdbc/xa/distCoffeeDB"); Context ctx = new InitialContext(); ctx.bind("jdbc/distCoffeeDB", ds);com.applogic.TransactionalDSandcom.dbaccess.XATransactionalDShave been deployed, an application can call the methodgetConnectionon instances of theTransactionalDSclass to get a connection to theCOFFEEBREAKdatabase that can be used in distributed transactions.
Using Connections for Distributed Transactions
To get a connection that can be used for distributed transactions, must use aDataSourceobject that has been properly implemented and deployed, as shown in the section Deploying Distributed Transactions. With such aDataSourceobject, call the methodgetConnectionon it. After you have the connection, use it just as you would use any other connection. Becausejdbc/distCoffeesDBhas been associated with anXADataSourceobject in a JNDI naming service, the following code produces aConnectionobject that can be used in distributed transactions:
There are some minor but important restrictions on how this connection is used while it is part of a distributed transaction. A transaction manager controls when a distributed transaction begins and when it is committed or rolled back; therefore, application code should never call the methodsContext ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB"); Connection con = ds.getConnection();Connection.commitorConnection.rollback. An application should likewise never callConnection.setAutoCommit(true), which enables the auto-commit mode, because that would also interfere with the transaction manager's control of the transaction boundaries. This explains why a new connection that is created in the scope of a distributed transaction has its auto-commit mode disabled by default. Note that these restrictions apply only when a connection is participating in a distributed transaction; there are no restrictions while the connection is not part of a distributed transaction.
For the following example, suppose that an order of coffee has been shipped, which triggers updates to two tables that reside on different DBMS servers. The first table is a newINVENTORYtable, and the second is theCOFFEEStable. Because these tables are on different DBMS servers, a transaction that involves both of them will be a distributed transaction. The code in the following example, which obtains a connection, updates theCOFFEEStable, and closes the connection, is the second part of a distributed transaction.
Note that the code does not explicitly commit or roll back the updates because the scope of the distributed transaction is being controlled by the middle tier server's underlying system infrastructure. Also, assuming that the connection used for the distributed transaction is a pooled connection, the application uses afinallyblock to close the connection. This guarantees that a valid connection will be closed even if an exception is thrown, thereby ensuring that the connection is returned to the connection pool to be recycled.
The following code sample illustrates an enterprise Bean, which is a class that implements the methods that can be called by a client computer. The purpose of this example is to demonstrate that application code for a distributed transaction is no different from other code except that it does not call theConnectionmethodscommit,rollback, orsetAutoCommit(true). Therefore, you do not need to worry about understanding the EJB API that is used.
import java.sql.*; import javax.sql.*; import javax.ejb.*; import javax.naming.*; public class DistributedTransactionBean implements SessionBean { // ... public void ejbCreate() throws CreateException { ctx = new InitialContext(); ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB"); } public void updateTotal(int incr, String cofName, String username, String password) throws SQLException { Connection con; PreparedStatement pstmt; try { con = ds.getConnection(username, password); pstmt = con.prepareStatement("UPDATE COFFEES " + "SET TOTAL = TOTAL + ? " + "WHERE COF_NAME = ?"); pstmt.setInt(1, incr); pstmt.setString(2, cofName); pstmt.executeUpdate(); stmt.close(); } finally { if (con != null) con.close(); } } private DataSource ds = null; private Context ctx = null; }Handling SQLExceptions
This page covers the following topics:
- Overview of SQLException
- Retrieving Exceptions
- Retrieving Warnings
- Categorized SQLExceptions
- Other Subclasses of SQLException
Overview of SQLException
When JDBC encounters an error during an interaction with a data source, it throws an instance ofSQLExceptionas opposed toException. (A data source in this context represents the database to which aConnectionobject is connected.) TheSQLExceptioninstance contains the following information that can help you determine the cause of the error:
-
A description of the error. Retrieve the
Stringobject that contains this description by calling the methodSQLException.getMessage.
-
A SQLState code. These codes and their respective meanings have been
standardized by ISO/ANSI and Open Group (X/Open), although some codes
have been reserved for database vendors to define for themselves. This
Stringobject consists of five alphanumeric characters. Retrieve this code by calling the methodSQLException.getSQLState.
-
An error code. This is an integer value identifying the error that caused the
SQLExceptioninstance to be thrown. Its value and meaning are implementation-specific and might be the actual error code returned by the underlying data source. Retrieve the error by calling the methodSQLException.getErrorCode.
-
A cause. A
SQLExceptioninstance might have a causal relationship, which consists of one or moreThrowableobjects that caused theSQLExceptioninstance to be thrown. To navigate this chain of causes, recursively call the methodSQLException.getCauseuntil anullvalue is returned.
-
A reference to any chained exceptions. If more than one
error occurs, the exceptions are referenced through this chain. Retrieve
these exceptions by calling the method
SQLException.getNextExceptionon the exception that was thrown.
Retrieving Exceptions
The following method,JDBCTutorialUtilities.printSQLExceptionoutputs the SQLState, error code, error description, and cause (if there is one) contained in theSQLExceptionas well as any other exception chained to it:
For example, if you call the methodpublic static void printSQLException(SQLException ex) { for (Throwable e : ex) { if (e instanceof SQLException) { if (ignoreSQLException( ((SQLException)e). getSQLState()) == false) { e.printStackTrace(System.err); System.err.println("SQLState: " + ((SQLException)e).getSQLState()); System.err.println("Error Code: " + ((SQLException)e).getErrorCode()); System.err.println("Message: " + e.getMessage()); Throwable t = ex.getCause(); while(t != null) { System.out.println("Cause: " + t); t = t.getCause(); } } } } }CoffeesTable.dropTablewith Java DB as your DBMS, the tableCOFFEESdoes not exist, and you remove the call toJDBCTutorialUtilities.ignoreSQLException, the output will be similar to the following:
Instead of outputtingSQLState: 42Y55 Error Code: 30000 Message: 'DROP TABLE' cannot be performed on 'TESTDB.COFFEES' because it does not exist.
SQLExceptioninformation, you could instead first retrieve theSQLStatethen process theSQLExceptionaccordingly. For example, the methodJDBCTutorialUtilities.ignoreSQLExceptionreturnstrueif theSQLStateis equal to code42Y55(and you are using Java DB as your DBMS), which causesJDBCTutorialUtilities.printSQLExceptionto ignore theSQLException:
public static boolean ignoreSQLException(String sqlState) { if (sqlState == null) { System.out.println("The SQL state is not defined!"); return false; } // X0Y32: Jar file already exists in schema if (sqlState.equalsIgnoreCase("X0Y32")) return true; // 42Y55: Table already exists in schema if (sqlState.equalsIgnoreCase("42Y55")) return true; return false; }Retrieving Warnings
SQLWarningobjects are a subclass ofSQLExceptionthat deal with database access warnings. Warnings do not stop the execution of an application, as exceptions do; they simply alert the user that something did not happen as planned. For example, a warning might let you know that a privilege you attempted to revoke was not revoked. Or a warning might tell you that an error occurred during a requested disconnection.
A warning can be reported on aConnectionobject, aStatementobject (includingPreparedStatementandCallableStatementobjects), or aResultSetobject. Each of these classes has agetWarningsmethod, which you must invoke in order to see the first warning reported on the calling object. IfgetWarningsreturns a warning, you can call theSQLWarningmethodgetNextWarningon it to get any additional warnings. Executing a statement automatically clears the warnings from a previous statement, so they do not build up. This means, however, that if you want to retrieve warnings reported on a statement, you must do so before you execute another statement.
The following methods fromJDBCTutorialUtilitiesillustrate how to get complete information about any warnings reported onStatementorResultSetobjects:
The most common warning is apublic static void getWarningsFromResultSet(ResultSet rs) throws SQLException { JDBCTutorialUtilities.printWarnings(rs.getWarnings()); } public static void getWarningsFromStatement(Statement stmt) throws SQLException { JDBCTutorialUtilities.printWarnings(stmt.getWarnings()); } public static void printWarnings(SQLWarning warning) throws SQLException { if (warning != null) { System.out.println("\n---Warning---\n"); while (warning != null) { System.out.println("Message: " + warning.getMessage()); System.out.println("SQLState: " + warning.getSQLState()); System.out.print("Vendor error code: "); System.out.println(warning.getErrorCode()); System.out.println(""); warning = warning.getNextWarning(); } }DataTruncationwarning, a subclass ofSQLWarning. AllDataTruncationobjects have a SQLState of01004, indicating that there was a problem with reading or writing data.DataTruncationmethods let you find out in which column or parameter data was truncated, whether the truncation was on a read or write operation, how many bytes should have been transferred, and how many bytes were actually transferred.
Categorized SQLExceptions
Your JDBC driver might throw a subclass ofSQLExceptionthat corresponds to a common SQLState or a common error state that is not associated with a specific SQLState class value. This enables you to write more portable error-handling code. These exceptions are subclasses of one of the following classes:
SQLNonTransientExceptionSQLTransientExceptionSQLRecoverableException
java.sqlpackage or the documentation of your JDBC driver for more information about these subclasses.
Other Subclasses of SQLException
The following subclasses ofSQLExceptioncan also be thrown:
BatchUpdateExceptionis thrown when an error occurs during a batch update operation. In addition to the information provided bySQLException,BatchUpdateExceptionprovides the update counts for all statements that were executed before the error occurred.SQLClientInfoExceptionis thrown when one or more client information properties could not be set on a Connection. In addition to the information provided bySQLException,SQLClientInfoExceptionprovides a list of client information properties that were not set.
Setting Up Tables
This page describes all the tables used in the JDBC tutorial and how to create them:
- COFFEES Table
- SUPPLIERS Table
- COF_INVENTORY Table
- MERCH_INVENTORY Table
- COFFEE_HOUSES Table
- DATA_REPOSITORY Table
- Creating Tables
- Populating Tables
COFFEES Table
TheCOFFEEStable stores information about the coffees available for sale at The Coffee Break:
The following describes each of the columns in theCOF_NAMESUP_IDPRICESALESTOTALColombian 101 7.99 0 0 French_Roast 49 8.99 0 0 Espresso 150 9.99 0 0 Colombian_Decaf 101 8.99 0 0 French_Roast_Decaf 49 9.99 0 0 COFFEEStable:
COF_NAME: Stores the coffee name. Holds values with a SQL type ofVARCHARwith a maximum length of 32 characters. Because the names are different for each type of coffee sold, the name uniquely identifies a particular coffee and serves as the primary key.SUP_ID: Stores a number identifying the coffee supplier. Holds values with a SQL type ofINTEGER. It is defined as a foreign key that references the columnSUP_IDin theSUPPLIERStable. Consequently, the DBMS will enforce that each value in this column matches one of the values in the corresponding column in theSUPPLIERStable.PRICE: Stores the cost of the coffee per pound. Holds values with a SQL type ofFLOATbecause it needs to hold values with decimal points. (Note that money values would typically be stored in a SQL typeDECIMALorNUMERIC, but because of differences among DBMSs and to avoid incompatibility with earlier versions of JDBC, the tutorial uses the more standard typeFLOAT.)SALES: Stores the number of pounds of coffee sold during the current week. Holds values with a SQL type ofINTEGER.TOTAL: Stores the number of pounds of coffee sold to date. Holds values with a SQL type ofINTEGER.
SUPPLIERS Table
TheSUPPLIERSstores information about each of the suppliers:
The following describes each of the columns in theSUP_IDSUP_NAMESTREETCITYSTATEZIP101 Acme, Inc. 99 Market Street Groundsville CA 95199 49 Superior Coffee 1 Party Place Mendocino CA 95460 150 The High Ground 100 Coffee Lane Meadows CA 93966 SUPPLIERStable:
SUP_ID: Stores a number identifying the coffee supplier. Holds values with a SQL type ofINTEGER. It is the primary key in this table.SUP_NAME: Stores the name of the coffee supplier.STREET,CITY,STATE, andZIP: These columns store the address of the coffee supplier.
COF_INVENTORY Table
The tableCOF_INVENTORYstores information about the amount of coffee stored in each warehouse:
The following describes each of the columns in theWAREHOUSE_IDCOF_NAMESUP_IDQUANDATE_VAL1234 House_Blend 49 0 2006_04_01 1234 House_Blend_Decaf 49 0 2006_04_01 1234 Colombian 101 0 2006_04_01 1234 French_Roast 49 0 2006_04_01 1234 Espresso 150 0 2006_04_01 1234 Colombian_Decaf 101 0 2006_04_01 COF_INVENTORYtable:
WAREHOUSE_ID: Stores a number identifying a warehouse.COF_NAME: Stores the name of a particular type of coffee.SUP_ID: Stores a number identifying a supplier.QUAN: Stores a number indicating the amount of merchandise available.DATE: Stores a timestamp value indicating the last time the row was updated.
MERCH_INVENTORY Table
The tableMERCH_INVENTORYstores information about the amount of non-coffee merchandise in stock:
The following describes each of the columns in theITEM_IDITEM_NAMESUP_IDQUANDATE00001234 Cup_Large 00456 28 2006_04_01 00001235 Cup_Small 00456 36 2006_04_01 00001236 Saucer 00456 64 2006_04_01 00001287 Carafe 00456 12 2006_04_01 00006931 Carafe 00927 3 2006_04_01 00006935 PotHolder 00927 88 2006_04_01 00006977 Napkin 00927 108 2006_04_01 00006979 Towel 00927 24 2006_04_01 00004488 CofMaker 08732 5 2006_04_01 00004490 CofGrinder 08732 9 2006_04_01 00004495 EspMaker 08732 4 2006_04_01 00006914 Cookbook 00927 12 2006_04_01 MERCH_INVENTORYtable:
ITEM_ID: Stores a number identifying an item.ITEM_NAME: Stores the name of an item.SUP_ID: Stores a number identifying a supplier.QUAN: Stores a number indicating the amount of that item available.DATE: Stores a timestamp value indicating the last time the row was updated.
COFFEE_HOUSES Table
The tableCOFFEE_HOUSESstores locations of coffee houses:
The following describes each of the columns in theSTORE_IDCITYCOFFEEMERCHTOTAL10023 Mendocino 3450 2005 5455 33002 Seattle 4699 3109 7808 10040 SF 5386 2841 8227 32001 Portland 3147 3579 6726 10042 SF 2863 1874 4710 10024 Sacramento 1987 2341 4328 10039 Carmel 2691 1121 3812 10041 LA 1533 1007 2540 33005 Olympia 2733 1550 4283 33010 Seattle 3210 2177 5387 10035 SF 1922 1056 2978 10037 LA 2143 1876 4019 10034 San_Jose 1234 1032 2266 32004 Eugene 1356 1112 2468 COFFEE_HOUSEStable:
STORE_ID: Stores a number identifying a coffee house. It indicates, among other things, the state in which the coffee house is located. A value beginning with 10, for example, means that the state is California.STORE_IDvalues beginning with 32 indicate Oregon, and those beginning with 33 indicate the state of Washington.CITY: Stores the name of the city in which the coffee house is located.COFFEE: Stores a number indicating the amount of coffee sold.MERCH: Stores a number indicating the amount of merchandise sold.TOTAL: Stores a number indicating the total amount of coffee and merchandise sold.
DATA_REPOSITORY Table
The table DATA_REPOSITORY stores URLs that reference documents and other data of interest to The Coffee Break. The scriptpopulate_tables.sqldoes not add any data to this table. The following describes each of the columns in this table:
DOCUMENT_NAME: Stores a string that identifies the URL.URL: Stores a URL.
Creating Tables
You can create tables with Apache Ant or JDBC API.
Creating Tables with Apache Ant
To create the tables used with the tutorial sample code, run the following command in the directory<JDBC tutorial directory>:
This command runs several Ant targets, including the following,ant setup
build-tables(from thebuild.xmlfile):
The sample specifies values for the following<target name="build-tables" description="Create database tables"> <sql driver="${DB.DRIVER}" url="${DB.URL}" userid="${DB.USER}" password="${DB.PASSWORD}" classpathref="CLASSPATH" delimiter="${DB.DELIMITER}" autocommit="false" onerror="abort"> <transaction src= "./sql/${DB.VENDOR}/create-tables.sql"/> </sql> </target>sqlAnt task parameters:
The sample stores the values of these parameters in a separate file. The build fileParameter Description driverFully qualified class name of your JDBC driver. This sample uses org.apache.derby.jdbc.EmbeddedDriverfor Java DB andcom.mysql.jdbc.Driverfor MySQL Connector/J.urlDatabase connection URL that your DBMS JDBC driver uses to connect to a database. useridName of a valid user in your DBMS. passwordPassword of the user specified in useridclasspathrefFull path name of the JAR file that contains the class specified in driverdelimiterString or character that separates SQL statements. This sample uses the semicolon ( ;).autocommitBoolean value; if set to false, all SQL statements are executed as one transaction.onerrorAction to perform when a statement fails; possible values are continue,stop, andabort. The valueabortspecifies that if an error occurs, the transaction is aborted.build.xmlretrieves these values with theimporttask:
The<import file="${ANTPROPERTIES}"/>transactionelement specifies a file that contains SQL statements to execute. The filecreate-tables.sqlcontains SQL statements that create all the tables described on this page. For example, the following excerpt from this file creates the tablesSUPPLIERSandCOFFEES:
Note: The filecreate table SUPPLIERS (SUP_ID integer NOT NULL, SUP_NAME varchar(40) NOT NULL, STREET varchar(40) NOT NULL, CITY varchar(20) NOT NULL, STATE char(2) NOT NULL, ZIP char(5), PRIMARY KEY (SUP_ID)); create table COFFEES (COF_NAME varchar(32) NOT NULL, SUP_ID int NOT NULL, PRICE numeric(10,2) NOT NULL, SALES integer NOT NULL, TOTAL integer NOT NULL, PRIMARY KEY (COF_NAME), FOREIGN KEY (SUP_ID) REFERENCES SUPPLIERS (SUP_ID));build.xmlcontains another target nameddrop-tablesthat deletes the tables used by the tutorial. Thesetuptarget runsdrop-tablesbefore running thebuild-tablestarget.
Creating Tables with JDBC API
The following method,SuppliersTable.createTable, creates theSUPPLIERStable:
The following method,public void createTable() throws SQLException { String createString = "create table " + dbName + ".SUPPLIERS " + "(SUP_ID integer NOT NULL, " + "SUP_NAME varchar(40) NOT NULL, " + "STREET varchar(40) NOT NULL, " + "CITY varchar(20) NOT NULL, " + "STATE char(2) NOT NULL, " + "ZIP char(5), " + "PRIMARY KEY (SUP_ID))"; Statement stmt = null; try { stmt = con.createStatement(); stmt.executeUpdate(createString); } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }CoffeesTable.createTable, creates theCOFFEEStable:
In both methods,public void createTable() throws SQLException { String createString = "create table " + dbName + ".COFFEES " + "(COF_NAME varchar(32) NOT NULL, " + "SUP_ID int NOT NULL, " + "PRICE float NOT NULL, " + "SALES integer NOT NULL, " + "TOTAL integer NOT NULL, " + "PRIMARY KEY (COF_NAME), " + "FOREIGN KEY (SUP_ID) REFERENCES " + dbName + ".SUPPLIERS (SUP_ID))"; Statement stmt = null; try { stmt = con.createStatement(); stmt.executeUpdate(createString); } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }conis aConnectionobject anddbNameis the name of the database in which you are creating the table.
To execute the SQL query, such as those specified by theStringcreateString, use aStatementobject. To create aStatementobject, call the methodConnection.createStatementfrom an existingConnectionobject. To execute a SQL query, call the methodStatement.executeUpdate.
AllStatementobjects are closed when the connection that created them is closed. However, it is good coding practice to explicitly closeStatementobjects as soon as you are finished with them. This allows any external resources that the statement is using to be released immediately. Close a statement by calling the methodStatement.close. Place this statement in afinallyto ensure that it closes even if the normal program flow is interrupted because an exception (such asSQLException) is thrown.
Note: You must create theSUPPLIERStable before theCOFFEESbecauseCOFFEEScontains a foreign key,SUP_IDthat referencesSUPPLIERS.
Populating Tables
Similarly, you can insert data into tables with Apache Ant or JDBC API.
Populating Tables with Apache Ant
In addition to creating the tables used by this tutorial, the commandant setupalso populates these tables. This command runs the Ant targetpopulate-tables, which runs the SQL scriptpopulate-tables.sql.
The following is an excerpt frompopulate-tables.sqlthat populates the tablesSUPPLIERSandCOFFEES:
insert into SUPPLIERS values( 49, 'Superior Coffee', '1 Party Place', 'Mendocino', 'CA', '95460'); insert into SUPPLIERS values( 101, 'Acme, Inc.', '99 Market Street', 'Groundsville', 'CA', '95199'); insert into SUPPLIERS values( 150, 'The High Ground', '100 Coffee Lane', 'Meadows', 'CA', '93966'); insert into COFFEES values( 'Colombian', 00101, 7.99, 0, 0); insert into COFFEES values( 'French_Roast', 00049, 8.99, 0, 0); insert into COFFEES values( 'Espresso', 00150, 9.99, 0, 0); insert into COFFEES values( 'Colombian_Decaf', 00101, 8.99, 0, 0); insert into COFFEES values( 'French_Roast_Decaf', 00049, 9.99, 0, 0);Populating Tables with JDBC API
The following method,SuppliersTable.populateTable, inserts data into the table:
The following method,public void populateTable() throws SQLException { Statement stmt = null; try { stmt = con.createStatement(); stmt.executeUpdate( "insert into " + dbName + ".SUPPLIERS " + "values(49, 'Superior Coffee', " + "'1 Party Place', " + "'Mendocino', 'CA', '95460')"); stmt.executeUpdate( "insert into " + dbName + ".SUPPLIERS " + "values(101, 'Acme, Inc.', " + "'99 Market Street', " + "'Groundsville', 'CA', '95199')"); stmt.executeUpdate( "insert into " + dbName + ".SUPPLIERS " + "values(150, " + "'The High Ground', " + "'100 Coffee Lane', " + "'Meadows', 'CA', '93966')"); } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }CoffeesTable.populateTable, inserts data into the table:
public void populateTable() throws SQLException { Statement stmt = null; try { stmt = con.createStatement(); stmt.executeUpdate( "insert into " + dbName + ".COFFEES " + "values('Colombian', 00101, " + "7.99, 0, 0)"); stmt.executeUpdate( "insert into " + dbName + ".COFFEES " + "values('French_Roast', " + "00049, 8.99, 0, 0)"); stmt.executeUpdate( "insert into " + dbName + ".COFFEES " + "values('Espresso', 00150, 9.99, 0, 0)"); stmt.executeUpdate( "insert into " + dbName + ".COFFEES " + "values('Colombian_Decaf', " + "00101, 8.99, 0, 0)"); stmt.executeUpdate( "insert into " + dbName + ".COFFEES " + "values('French_Roast_Decaf', " + "00049, 9.99, 0, 0)"); } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }The following method,Retrieving and Modifying Values from Result Sets
CoffeesTable.viewTableoutputs the contents of theCOFFEEStables, and demonstrates the use ofResultSetobjects and cursors:
Apublic static void viewTable(Connection con, String dbName) throws SQLException { Statement stmt = null; String query = "select COF_NAME, SUP_ID, PRICE, " + "SALES, TOTAL " + "from " + dbName + ".COFFEES"; try { stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); int supplierID = rs.getInt("SUP_ID"); float price = rs.getFloat("PRICE"); int sales = rs.getInt("SALES"); int total = rs.getInt("TOTAL"); System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }ResultSetobject is a table of data representing a database result set, which is usually generated by executing a statement that queries the database. For example, theCoffeeTables.viewTablemethod creates aResultSet,rs, when it executes the query through theStatementobject,stmt. Note that aResultSetobject can be created through any object that implements theStatementinterface, includingPreparedStatement,CallableStatement, andRowSet.
You access the data in aResultSetobject through a cursor. Note that this cursor is not a database cursor. This cursor is a pointer that points to one row of data in theResultSet. Initially, the cursor is positioned before the first row. The methodResultSet.nextmoves the cursor to the next row. This method returnsfalseif the cursor is positioned after the last row. This method repeatedly calls theResultSet.nextmethod with awhileloop to iterate through all the data in theResultSet.
This page covers the following topics:
- ResultSet Interface
- Retrieving Column Values from Rows
- Cursors
- Updating Rows in ResultSet Objects
- Using Statement Objects for Batch Updates
- Inserting Rows in ResultSet Objects
ResultSet Interface
TheResultSetinterface provides methods for retrieving and manipulating the results of executed queries, andResultSetobjects can have different functionality and characteristics. These characteristics are type, concurrency, and cursor holdability.
ResultSet Types
The type of aResultSetobject determines the level of its functionality in two areas: the ways in which the cursor can be manipulated, and how concurrent changes made to the underlying data source are reflected by theResultSetobject.
The sensitivity of aResultSetobject is determined by one of three differentResultSettypes:
TYPE_FORWARD_ONLY: The result set cannot be scrolled; its cursor moves forward only, from before the first row to after the last row. The rows contained in the result set depend on how the underlying database generates the results. That is, it contains the rows that satisfy the query at either the time the query is executed or as the rows are retrieved.TYPE_SCROLL_INSENSITIVE: The result can be scrolled; its cursor can move both forward and backward relative to the current position, and it can move to an absolute position. The result set is insensitive to changes made to the underlying data source while it is open. It contains the rows that satisfy the query at either the time the query is executed or as the rows are retrieved.TYPE_SCROLL_SENSITIVE: The result can be scrolled; its cursor can move both forward and backward relative to the current position, and it can move to an absolute position. The result set reflects changes made to the underlying data source while the result set remains open.
ResultSettype isTYPE_FORWARD_ONLY.
Note: Not all databases and JDBC drivers support allResultSettypes. The methodDatabaseMetaData.supportsResultSetTypereturnstrueif the specifiedResultSettype is supported andfalseotherwise.
ResultSet Concurrency
The concurrency of aResultSetobject determines what level of update functionality is supported.
There are two concurrency levels:
CONCUR_READ_ONLY: TheResultSetobject cannot be updated using theResultSetinterface.CONCUR_UPDATABLE: TheResultSetobject can be updated using theResultSetinterface.
ResultSetconcurrency isCONCUR_READ_ONLY.
Note: Not all JDBC drivers and databases support concurrency. The methodDatabaseMetaData.supportsResultSetConcurrencyreturnstrueif the specified concurrency level is supported by the driver andfalseotherwise.
The methodCoffeesTable.modifyPricesdemonstrates how to use aResultSetobject whose concurrency level isCONCUR_UPDATABLE.
Cursor Holdability
Calling the methodConnection.commitcan close theResultSetobjects that have been created during the current transaction. In some cases, however, this may not be the desired behavior. TheResultSetproperty holdability gives the application control over whetherResultSetobjects (cursors) are closed when commit is called.
The followingResultSetconstants may be supplied to theConnectionmethodscreateStatement,prepareStatement, andprepareCall:
HOLD_CURSORS_OVER_COMMIT:ResultSetcursors are not closed; they are holdable: they are held open when the methodcommitis called. Holdable cursors might be ideal if your application uses mostly read-onlyResultSetobjects.CLOSE_CURSORS_AT_COMMIT:ResultSetobjects (cursors) are closed when thecommitmethod is called. Closing cursors when this method is called can result in better performance for some applications.
Note: Not all JDBC drivers and databases support holdable and non-holdable cursors. The following method,JDBCTutorialUtilities.cursorHoldabilitySupport, outputs the default cursor holdability ofResultSetobjects and whetherHOLD_CURSORS_OVER_COMMITandCLOSE_CURSORS_AT_COMMITare supported:
public static void cursorHoldabilitySupport(Connection conn) throws SQLException { DatabaseMetaData dbMetaData = conn.getMetaData(); System.out.println("ResultSet.HOLD_CURSORS_OVER_COMMIT = " + ResultSet.HOLD_CURSORS_OVER_COMMIT); System.out.println("ResultSet.CLOSE_CURSORS_AT_COMMIT = " + ResultSet.CLOSE_CURSORS_AT_COMMIT); System.out.println("Default cursor holdability: " + dbMetaData.getResultSetHoldability()); System.out.println("Supports HOLD_CURSORS_OVER_COMMIT? " + dbMetaData.supportsResultSetHoldability( ResultSet.HOLD_CURSORS_OVER_COMMIT)); System.out.println("Supports CLOSE_CURSORS_AT_COMMIT? " + dbMetaData.supportsResultSetHoldability( ResultSet.CLOSE_CURSORS_AT_COMMIT)); }Retrieving Column Values from Rows
TheResultSetinterface declares getter methods (for example,getBooleanandgetLong) for retrieving column values from the current row. You can retrieve values using either the index number of the column or the alias or name of the column. The column index is usually more efficient. Columns are numbered from 1. For maximum portability, result set columns within each row should be read in left-to-right order, and each column should be read only once.
For example, the following method,CoffeesTable.alternateViewTable, retrieves column values by number:
Strings used as input to getter methods are case-insensitive. When a getter method is called with a string and more than one column has the same alias or name as the string, the value of the first matching column is returned. The option to use a string as opposed to an integer is designed to be used when column aliases and names are used in the SQL query that generated the result set. For columns that are not explicitly named in the query (for example,public static void alternateViewTable(Connection con) throws SQLException { Statement stmt = null; String query = "select COF_NAME, SUP_ID, PRICE, " + "SALES, TOTAL from COFFEES"; try { stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString(1); int supplierID = rs.getInt(2); float price = rs.getFloat(3); int sales = rs.getInt(4); int total = rs.getInt(5); System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }select * from COFFEES) it is best to use column numbers. If column names are used, the developer should guarantee that they uniquely refer to the intended columns by using column aliases. A column alias effectively renames the column of a result set. To specify a column alias, use the SQLASclause in theSELECTstatement.
The getter method of the appropriate type retrieves the value in each column. For example, in the methodCoffeeTables.viewTable, the first column in each row of theResultSetrsisCOF_NAME, which stores a value of SQL typeVARCHAR. The method for retrieving a value of SQL typeVARCHARisgetString. The second column in each row stores a value of SQL typeINTEGER, and the method for retrieving values of that type isgetInt.
Note that although the methodgetStringis recommended for retrieving the SQL typesCHARandVARCHAR, it is possible to retrieve any of the basic SQL types with it. Getting all values withgetStringcan be very useful, but it also has its limitations. For instance, if it is used to retrieve a numeric type,getStringconverts the numeric value to a JavaStringobject, and the value has to be converted back to a numeric type before it can be operated on as a number. In cases where the value is treated as a string anyway, there is no drawback. Furthermore, if you want an application to retrieve values of any standard SQL type other than SQL3 types, use thegetStringmethod.
Cursors
As mentioned previously, you access the data in aResultSetobject through a cursor, which points to one row in theResultSetobject. However, when aResultSetobject is first created, the cursor is positioned before the first row. The methodCoffeeTables.viewTablemoves the cursor by calling theResultSet.nextmethod. There are other methods available to move the cursor:
next: Moves the cursor forward one row. Returnstrueif the cursor is now positioned on a row andfalseif the cursor is positioned after the last row.previous: Moves the cursor backward one row. Returnstrueif the cursor is now positioned on a row andfalseif the cursor is positioned before the first row.first: Moves the cursor to the first row in theResultSetobject. Returnstrueif the cursor is now positioned on the first row andfalseif theResultSetobject does not contain any rows.last:: Moves the cursor to the last row in theResultSetobject. Returnstrueif the cursor is now positioned on the last row andfalseif theResultSetobject does not contain any rows.beforeFirst: Positions the cursor at the start of theResultSetobject, before the first row. If theResultSetobject does not contain any rows, this method has no effect.afterLast: Positions the cursor at the end of theResultSetobject, after the last row. If theResultSetobject does not contain any rows, this method has no effect.relative(int rows): Moves the cursor relative to its current position.absolute(int row): Positions the cursor on the row specified by the parameterrow.
ResultSetisTYPE_FORWARD_ONLY, which means that it cannot be scrolled; you cannot call any of these methods that move the cursor, exceptnext, if yourResultSetcannot be scrolled. The methodCoffeesTable.modifyPrices, described in the following section, demonstrates how you can move the cursor of aResultSet.
Updating Rows in ResultSet Objects
You cannot update a defaultResultSetobject, and you can only move its cursor forward. However, you can createResultSetobjects that can be scrolled (the cursor can move backwards or move to an absolute position) and updated.
The following method,CoffeesTable.modifyPrices, multiplies thePRICEcolumn of each row by the argumentpercentage:
The fieldpublic void modifyPrices(float percentage) throws SQLException { Statement stmt = null; try { stmt = con.createStatement(); stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet uprs = stmt.executeQuery( "SELECT * FROM " + dbName + ".COFFEES"); while (uprs.next()) { float f = uprs.getFloat("PRICE"); uprs.updateFloat( "PRICE", f * percentage); uprs.updateRow(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }ResultSet.TYPE_SCROLL_SENSITIVEcreates aResultSetobject whose cursor can move both forward and backward relative to the current position and to an absolute position. The fieldResultSet.CONCUR_UPDATABLEcreates aResultSetobject that can be updated. See theResultSetJavadoc for other fields you can specify to modify the behavior ofResultSetobjects.
The methodResultSet.updateFloatupdates the specified column (in this example,PRICEwith the specifiedfloatvalue in the row where the cursor is positioned.ResultSetcontains various updater methods that enable you to update column values of various data types. However, none of these updater methods modifies the database; you must call the methodResultSet.updateRowto update the database.
Using Statement Objects for Batch Updates
Statement,PreparedStatementandCallableStatementobjects have a list of commands that is associated with them. This list may contain statements for updating, inserting, or deleting a row; and it may also contain DDL statements such asCREATE TABLEandDROP TABLE. It cannot, however, contain a statement that would produce aResultSetobject, such as aSELECTstatement. In other words, the list can contain only statements that produce an update count.
The list, which is associated with aStatementobject at its creation, is initially empty. You can add SQL commands to this list with the methodaddBatchand empty it with the methodclearBatch. When you have finished adding statements to the list, call the methodexecuteBatchto send them all to the database to be executed as a unit, or batch.
For example, the following methodCoffeesTable.batchUpdateadds four rows to theCOFFEEStable with a batch update:
The following line disables auto-commit mode for thepublic void batchUpdate() throws SQLException { Statement stmt = null; try { this.con.setAutoCommit(false); stmt = this.con.createStatement(); stmt.addBatch( "INSERT INTO COFFEES " + "VALUES('Amaretto', 49, 9.99, 0, 0)"); stmt.addBatch( "INSERT INTO COFFEES " + "VALUES('Hazelnut', 49, 9.99, 0, 0)"); stmt.addBatch( "INSERT INTO COFFEES " + "VALUES('Amaretto_decaf', 49, " + "10.99, 0, 0)"); stmt.addBatch( "INSERT INTO COFFEES " + "VALUES('Hazelnut_decaf', 49, " + "10.99, 0, 0)"); int [] updateCounts = stmt.executeBatch(); this.con.commit(); } catch(BatchUpdateException b) { JDBCTutorialUtilities.printBatchUpdateException(b); } catch(SQLException ex) { JDBCTutorialUtilities.printSQLException(ex); } finally { if (stmt != null) { stmt.close(); } this.con.setAutoCommit(true); } }Connectionobject con so that the transaction will not be automatically committed or rolled back when the methodexecuteBatchis called.
To allow for correct error handling, you should always disable auto-commit mode before beginning a batch update.this.con.setAutoCommit(false);
The methodStatement.addBatchadds a command to the list of commands associated with theStatementobjectstmt. In this example, these commands are allINSERT INTOstatements, each one adding a row consisting of five column values. The values for the columnsCOF_NAMEandPRICEare the name of the coffee and its price, respectively. The second value in each row is 49 because that is the identification number for the supplier, Superior Coffee. The last two values, the entries for the columnsSALESandTOTAL, all start out being zero because there have been no sales yet. (SALESis the number of pounds of this row's coffee sold in the current week;TOTALis the total of all the cumulative sales of this coffee.)
The following line sends the four SQL commands that were added to its list of commands to the database to be executed as a batch:
Note thatint [] updateCounts = stmt.executeBatch();
stmtuses the methodexecuteBatchto send the batch of insertions, not the methodexecuteUpdate, which sends only one command and returns a single update count. The DBMS executes the commands in the order in which they were added to the list of commands, so it will first add the row of values for Amaretto, then add the row for Hazelnut, then Amaretto decaf, and finally Hazelnut decaf. If all four commands execute successfully, the DBMS will return an update count for each command in the order in which it was executed. The update counts that indicate how many rows were affected by each command are stored in the arrayupdateCounts.
If all four of the commands in the batch are executed successfully,updateCountswill contain four values, all of which are 1 because an insertion affects one row. The list of commands associated withstmtwill now be empty because the four commands added previously were sent to the database whenstmtcalled the methodexecuteBatch. You can at any time explicitly empty this list of commands with the methodclearBatch.
TheConnection.commitmethod makes the batch of updates to theCOFFEEStable permanent. This method needs to be called explicitly because the auto-commit mode for this connection was disabled previously.
The following line enables auto-commit mode for the currentConnectionobject.
Now each statement in the example will automatically be committed after it is executed, and it no longer needs to invoke the methodthis.con.setAutoCommit(true);
commit.
Performing Parameterized Batch Update
It is also possible to have a parameterized batch update, as shown in the following code fragment, whereconis aConnectionobject:
con.setAutoCommit(false); PreparedStatement pstmt = con.prepareStatement( "INSERT INTO COFFEES VALUES( " + "?, ?, ?, ?, ?)"); pstmt.setString(1, "Amaretto"); pstmt.setInt(2, 49); pstmt.setFloat(3, 9.99); pstmt.setInt(4, 0); pstmt.setInt(5, 0); pstmt.addBatch(); pstmt.setString(1, "Hazelnut"); pstmt.setInt(2, 49); pstmt.setFloat(3, 9.99); pstmt.setInt(4, 0); pstmt.setInt(5, 0); pstmt.addBatch(); // ... and so on for each new // type of coffee int [] updateCounts = pstmt.executeBatch(); con.commit(); con.setAutoCommit(true);Handling Batch Update Exceptions
You will get aBatchUpdateExceptionwhen you call the methodexecuteBatchif (1) one of the SQL statements you added to the batch produces a result set (usually a query) or (2) one of the SQL statements in the batch does not execute successfully for some other reason.
You should not add a query (aSELECTstatement) to a batch of SQL commands because the methodexecuteBatch, which returns an array of update counts, expects an update count from each SQL statement that executes successfully. This means that only commands that return an update count (commands such asINSERT INTO,UPDATE,DELETE) or that return 0 (such asCREATE TABLE,DROP TABLE,ALTER TABLE) can be successfully executed as a batch with theexecuteBatchmethod.
ABatchUpdateExceptioncontains an array of update counts that is similar to the array returned by the methodexecuteBatch. In both cases, the update counts are in the same order as the commands that produced them. This tells you how many commands in the batch executed successfully and which ones they are. For example, if five commands executed successfully, the array will contain five numbers: the first one being the update count for the first command, the second one being the update count for the second command, and so on.
BatchUpdateExceptionis derived fromSQLException. This means that you can use all of the methods available to anSQLExceptionobject with it. The following method,JDBCTutorialUtilities.printBatchUpdateExceptionprints all of theSQLExceptioninformation plus the update counts contained in aBatchUpdateExceptionobject. BecauseBatchUpdateException.getUpdateCountsreturns an array ofint, the code uses aforloop to print each of the update counts:
public static void printBatchUpdateException(BatchUpdateException b) { System.err.println("----BatchUpdateException----"); System.err.println("SQLState: " + b.getSQLState()); System.err.println("Message: " + b.getMessage()); System.err.println("Vendor: " + b.getErrorCode()); System.err.print("Update counts: "); int [] updateCounts = b.getUpdateCounts(); for (int i = 0; i < updateCounts.length; i++) { System.err.print(updateCounts[i] + " "); } }Inserting Rows in ResultSet Objects
Note: Not all JDBC drivers support inserting new rows with theResultSetinterface. If you attempt to insert a new row and your JDBC driver database does not support this feature, aSQLFeatureNotSupportedExceptionexception is thrown.
The following method,CoffeesTable.insertRow, inserts a row into theCOFFEESthrough aResultSetobject:
This example calls thepublic void insertRow(String coffeeName, int supplierID, float price, int sales, int total) throws SQLException { Statement stmt = null; try { stmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE ResultSet.CONCUR_UPDATABLE); ResultSet uprs = stmt.executeQuery( "SELECT * FROM " + dbName + ".COFFEES"); uprs.moveToInsertRow(); uprs.updateString("COF_NAME", coffeeName); uprs.updateInt("SUP_ID", supplierID); uprs.updateFloat("PRICE", price); uprs.updateInt("SALES", sales); uprs.updateInt("TOTAL", total); uprs.insertRow(); uprs.beforeFirst(); } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmt != null) { stmt.close(); } } }Connection.createStatementmethod with two arguments,ResultSet.TYPE_SCROLL_SENSITIVEandResultSet.CONCUR_UPDATABLE. The first value enables the cursor of theResultSetobject to be moved both forward and backward. The second value,ResultSet.CONCUR_UPDATABLE, is required if you want to insert rows into aResultSetobject; it specifies that it can be updatable.
The same stipulations for using strings in getter methods also apply to updater methods.
The methodResultSet.moveToInsertRowmoves the cursor to the insert row. The insert row is a special row associated with an updatable result set. It is essentially a buffer where a new row can be constructed by calling the updater methods prior to inserting the row into the result set. For example, this method calls the methodResultSet.updateStringto update the insert row'sCOF_NAMEcolumn toKona.
The methodResultSet.insertRowinserts the contents of the insert row into theResultSetobject and into the database.
Note: After inserting a row with theResultSet.insertRow, you should move the cursor to a row other than the insert row. For example, this example moves it to before the first row in the result set with the methodResultSet.beforeFirst. Unexpected results can occur if another part of your application uses the same result set and the cursor is still pointing to the insert row.
Using Prepared Statements
This page covers the following topics:
- Overview of Prepared Statements
- Creating a PreparedStatement Object
- Supplying Values for PreparedStatement Parameters
Overview of Prepared Statements
Sometimes it is more convenient to use aPreparedStatementobject for sending SQL statements to the database. This special type of statement is derived from the more general class,Statement, that you already know.
If you want to execute aStatementobject many times, it usually reduces execution time to use aPreparedStatementobject instead.
The main feature of aPreparedStatementobject is that, unlike aStatementobject, it is given a SQL statement when it is created. The advantage to this is that in most cases, this SQL statement is sent to the DBMS right away, where it is compiled. As a result, thePreparedStatementobject contains not just a SQL statement, but a SQL statement that has been precompiled. This means that when thePreparedStatementis executed, the DBMS can just run thePreparedStatementSQL statement without having to compile it first.
AlthoughPreparedStatementobjects can be used for SQL statements with no parameters, you probably use them most often for SQL statements that take parameters. The advantage of using SQL statements that take parameters is that you can use the same statement and supply it with different values each time you execute it. Examples of this are in the following sections.
The following method,CoffeesTable.updateCoffeeSales, stores the number of pounds of coffee sold in the current week in theSALEScolumn for each type of coffee, and updates the total number of pounds of coffee sold in theTOTALcolumn for each type of coffee:
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { PreparedStatement updateSales = null; PreparedStatement updateTotal = null; String updateString = "update " + dbName + ".COFFEES " + "set SALES = ? where COF_NAME = ?"; String updateStatement = "update " + dbName + ".COFFEES " + "set TOTAL = TOTAL + ? " + "where COF_NAME = ?"; try { con.setAutoCommit(false); updateSales = con.prepareStatement(updateString); updateTotal = con.prepareStatement(updateStatement); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); if (con != null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch(SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } finally { if (updateSales != null) { updateSales.close(); } if (updateTotal != null) { updateTotal.close(); } con.setAutoCommit(true); } }Creating a PreparedStatement Object
The following creates aPreparedStatementobject that takes two input parameters:
String updateString = "update " + dbName + ".COFFEES " + "set SALES = ? where COF_NAME = ?"; updateSales = con.prepareStatement(updateString);Supplying Values for PreparedStatement Parameters
You must supply values in place of the question mark placeholders (if there are any) before you can execute aPreparedStatementobject. Do this by calling one of the setter methods defined in thePreparedStatementclass. The following statements supply the two question mark placeholders in thePreparedStatementnamedupdateSales:
The first argument for each of these setter methods specifies the question mark placeholder. In this example,updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey());
setIntspecifies the first placeholder andsetStringspecifies the second placeholder.
After a parameter has been set with a value, it retains that value until it is reset to another value, or the methodclearParametersis called. Using thePreparedStatementobjectupdateSales, the following code fragment illustrates reusing a prepared statement after resetting the value of one of its parameters and leaving the other one the same:
// changes SALES column of French Roast //row to 100 updateSales.setInt(1, 100); updateSales.setString(2, "French_Roast"); updateSales.executeUpdate(); // changes SALES column of Espresso row to 100 // (the first parameter stayed 100, and the second // parameter was reset to "Espresso") updateSales.setString(2, "Espresso"); updateSales.executeUpdate();
Using Loops to Set Values
You can often make coding easier by using aforloop or awhileloop to set values for input parameters.
TheCoffeesTable.updateCoffeeSalesmethod uses a for-each loop to repeatedly set values in thePreparedStatementobjectsupdateSalesandupdateTotal:
The methodfor (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); // ... }CoffeesTable.updateCoffeeSalestakes one argument,HashMap. Each element in theHashMapargument contains the name of one type of coffee and the number of pounds of that type of coffee sold during the current week. The for-each loop iterates through each element of theHashMapargument and sets the appropriate question mark placeholders inupdateSalesandupdateTotal.
Executing PreparedStatement Objects
As withStatementobjects, to execute aPreparedStatementobject, call an execute statement:executeQueryif the query returns only oneResultSet(such as aSELECTSQL statement),executeUpdateif the query does not return aResultSet(such as anUPDATESQL statement), orexecuteif the query might return more than oneResultSetobject. BothPreparedStatementobjects inCoffeesTable.updateCoffeeSalescontainUPDATESQL statements, so both are executed by callingexecuteUpdate:
No arguments are supplied toupdateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();con.commit();executeUpdatewhen they are used to executeupdateSalesandupdateTotals; bothPreparedStatementobjects already contain the SQL statement to be executed.
Note: At the beginning ofCoffeesTable.updateCoffeeSales, the auto-commit mode is set to false:
Consequently, no SQL statements are committed until the methodcon.setAutoCommit(false);
commitis called. For more information about the auto-commit mode, see Transactions.
Return Values for the executeUpdate Method
WhereasexecuteQueryreturns aResultSetobject containing the results of the query sent to the DBMS, the return value forexecuteUpdateis anintvalue that indicates how many rows of a table were updated. For instance, the following code shows the return value ofexecuteUpdatebeing assigned to the variablen:
The tableupdateSales.setInt(1, 50); updateSales.setString(2, "Espresso"); int n = updateSales.executeUpdate(); // n = 1 because one row had a change in it
COFFEESis updated; the value 50 replaces the value in the columnSALESin the row forEspresso. That update affects one row in the table, sonis equal to 1.
When the methodexecuteUpdateis used to execute a DDL (data definition language) statement, such as in creating a table, it returns theintvalue of 0. Consequently, in the following code fragment, which executes the DDL statement used to create the tableCOFFEES,nis assigned a value of 0:
Note that when the return value for// n = 0 int n = executeUpdate(createTableCoffees);
executeUpdateis 0, it can mean one of two things:
- The statement executed was an update statement that affected zero rows.
- The statement executed was a DDL statement.
Using Transactions
There are times when you do not want one statement to take effect unless another one completes. For example, when the proprietor of The Coffee Break updates the amount of coffee sold each week, the proprietor will also want to update the total amount sold to date. However, the amount sold per week and the total amount sold should be updated at the same time; otherwise, the data will be inconsistent. The way to be sure that either both actions occur or neither action occurs is to use a transaction. A transaction is a set of one or more statements that is executed as a unit, so either all of the statements are executed, or none of the statements is executed.
This page covers the following topics
- Disabling Auto-Commit Mode
- Committing Transactions
- Using Transactions to Preserve Data Integrity
- Setting and Rolling Back to Savepoints
- Releasing Savepoints
- When to Call Method rollback
Disabling Auto-Commit Mode
When a connection is created, it is in auto-commit mode. This means that each individual SQL statement is treated as a transaction and is automatically committed right after it is executed. (To be more precise, the default is for a SQL statement to be committed when it is completed, not when it is executed. A statement is completed when all of its result sets and update counts have been retrieved. In almost all cases, however, a statement is completed, and therefore committed, right after it is executed.)
The way to allow two or more statements to be grouped into a transaction is to disable the auto-commit mode. This is demonstrated in the following code, whereconis an active connection:
con.setAutoCommit(false);
Committing Transactions
After the auto-commit mode is disabled, no SQL statements are committed until you call the methodcommitexplicitly. All statements executed after the previous call to the methodcommitare included in the current transaction and committed together as a unit. The following method,CoffeesTable.updateCoffeeSales, in whichconis an active connection, illustrates a transaction:
In this method, the auto-commit mode is disabled for the connectionpublic void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { PreparedStatement updateSales = null; PreparedStatement updateTotal = null; String updateString = "update " + dbName + ".COFFEES " + "set SALES = ? where COF_NAME = ?"; String updateStatement = "update " + dbName + ".COFFEES " + "set TOTAL = TOTAL + ? " + "where COF_NAME = ?"; try { con.setAutoCommit(false); updateSales = con.prepareStatement(updateString); updateTotal = con.prepareStatement(updateStatement); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); if (con != null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch(SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } finally { if (updateSales != null) { updateSales.close(); } if (updateTotal != null) { updateTotal.close(); } con.setAutoCommit(true); } }con, which means that the two prepared statementsupdateSalesandupdateTotalare committed together when the methodcommitis called. Whenever thecommitmethod is called (either automatically when auto-commit mode is enabled or explicitly when it is disabled), all changes resulting from statements in the transaction are made permanent. In this case, that means that theSALESandTOTALcolumns for Colombian coffee have been changed to50(ifTOTALhad been0previously) and will retain this value until they are changed with another update statement.
The statementcon.setAutoCommit(true);enables auto-commit mode, which means that each statement is once again committed automatically when it is completed. Then, you are back to the default state where you do not have to call the methodcommityourself. It is advisable to disable the auto-commit mode only during the transaction mode. This way, you avoid holding database locks for multiple statements, which increases the likelihood of conflicts with other users.
Using Transactions to Preserve Data Integrity
In addition to grouping statements together for execution as a unit, transactions can help to preserve the integrity of the data in a table. For instance, imagine that an employee was supposed to enter new coffee prices in the tableCOFFEESbut delayed doing it for a few days. In the meantime, prices rose, and today the owner is in the process of entering the higher prices. The employee finally gets around to entering the now outdated prices at the same time that the owner is trying to update the table. After inserting the outdated prices, the employee realizes that they are no longer valid and calls theConnectionmethodrollbackto undo their effects. (The methodrollbackaborts a transaction and restores values to what they were before the attempted update.) At the same time, the owner is executing aSELECTstatement and printing the new prices. In this situation, it is possible that the owner will print a price that had been rolled back to its previous value, making the printed price incorrect.
This kind of situation can be avoided by using transactions, providing some level of protection against conflicts that arise when two users access data at the same time.
To avoid conflicts during a transaction, a DBMS uses locks, mechanisms for blocking access by others to the data that is being accessed by the transaction. (Note that in auto-commit mode, where each statement is a transaction, locks are held for only one statement.) After a lock is set, it remains in force until the transaction is committed or rolled back. For example, a DBMS could lock a row of a table until updates to it have been committed. The effect of this lock would be to prevent a user from getting a dirty read, that is, reading a value before it is made permanent. (Accessing an updated value that has not been committed is considered a dirty read because it is possible for that value to be rolled back to its previous value. If you read a value that is later rolled back, you will have read an invalid value.)
How locks are set is determined by what is called a transaction isolation level, which can range from not supporting transactions at all to supporting transactions that enforce very strict access rules.
One example of a transaction isolation level isTRANSACTION_READ_COMMITTED, which will not allow a value to be accessed until after it has been committed. In other words, if the transaction isolation level is set toTRANSACTION_READ_COMMITTED, the DBMS does not allow dirty reads to occur. The interfaceConnectionincludes five values that represent the transaction isolation levels you can use in JDBC:
A non-repeatable read occurs when transaction A retrieves a row, transaction B subsequently updates the row, and transaction A later retrieves the same row again. Transaction A retrieves the same row twice but sees different data.Isolation Level Transactions Dirty Reads Non-Repeatable Reads Phantom Reads TRANSACTION_NONENot supported Not applicable Not applicable Not applicable TRANSACTION_READ_COMMITTEDSupported Prevented Allowed Allowed TRANSACTION_READ_UNCOMMITTEDSupported Allowed Allowed Allowed TRANSACTION_REPEATABLE_READSupported Prevented Prevented Allowed TRANSACTION_SERIALIZABLESupported Prevented Prevented Prevented
A phantom read occurs when transaction A retrieves a set of rows satisfying a given condition, transaction B subsequently inserts or updates a row such that the row now meets the condition in transaction A, and transaction A later repeats the conditional retrieval. Transaction A now sees an additional row. This row is referred to as a phantom.
Usually, you do not need to do anything about the transaction isolation level; you can just use the default one for your DBMS. The default transaction isolation level depends on your DBMS. For example, for Java DB, it isTRANSACTION_READ_COMMITTED. JDBC allows you to find out what transaction isolation level your DBMS is set to (using theConnectionmethodgetTransactionIsolation) and also allows you to set it to another level (using theConnectionmethodsetTransactionIsolation).
Note: A JDBC driver might not support all transaction isolation levels. If a driver does not support the isolation level specified in an invocation ofsetTransactionIsolation, the driver can substitute a higher, more restrictive transaction isolation level. If a driver cannot substitute a higher transaction level, it throws aSQLException. Use the methodDatabaseMetaData.supportsTransactionIsolationLevelto determine whether or not the driver supports a given level.
Setting and Rolling Back to Savepoints
The methodConnection.setSavepoint, sets aSavepointobject within the current transaction. TheConnection.rollbackmethod is overloaded to take aSavepointargument.
The following method,CoffeesTable.modifyPricesByPercentage, raises the price of a particular coffee by a percentage,priceModifier. However, if the new price is greater than a specified price,maximumPrice, then the price is reverted to the original price:
The following statement specifies that the cursor of thepublic void modifyPricesByPercentage( String coffeeName, float priceModifier, float maximumPrice) throws SQLException { con.setAutoCommit(false); Statement getPrice = null; Statement updatePrice = null; ResultSet rs = null; String query = "SELECT COF_NAME, PRICE FROM COFFEES " + "WHERE COF_NAME = '" + coffeeName + "'"; try { Savepoint save1 = con.setSavepoint(); getPrice = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); updatePrice = con.createStatement(); if (!getPrice.execute(query)) { System.out.println( "Could not find entry " + "for coffee named " + coffeeName); } else { rs = getPrice.getResultSet(); rs.first(); float oldPrice = rs.getFloat("PRICE"); float newPrice = oldPrice + (oldPrice * priceModifier); System.out.println( "Old price of " + coffeeName + " is " + oldPrice); System.out.println( "New price of " + coffeeName + " is " + newPrice); System.out.println( "Performing update..."); updatePrice.executeUpdate( "UPDATE COFFEES SET PRICE = " + newPrice + " WHERE COF_NAME = '" + coffeeName + "'"); System.out.println( "\nCOFFEES table after " + "update:"); CoffeesTable.viewTable(con); if (newPrice > maximumPrice) { System.out.println( "\nThe new price, " + newPrice + ", is greater than the " + "maximum price, " + maximumPrice + ". Rolling back the " + "transaction..."); con.rollback(save1); System.out.println( "\nCOFFEES table " + "after rollback:"); CoffeesTable.viewTable(con); } con.commit(); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { if (getPrice != null) { getPrice.close(); } if (updatePrice != null) { updatePrice.close(); } con.setAutoCommit(true); } }ResultSetobject generated from thegetPricequery is closed when thecommitmethod is called. Note that if your DBMs does not supportResultSet.CLOSE_CURSORS_AT_COMMIT, then this constant is ignored:
The method begins by creating agetPrice = con.prepareStatement(query, ResultSet.CLOSE_CURSORS_AT_COMMIT);
Savepointwith the following statement:
The method checks if the new price is greater than theSavepoint save1 = con.setSavepoint();
maximumPricevalue. If so, the method rolls back the transaction with the following statement:
Consequently, when the method commits the transaction by calling thecon.rollback(save1);
Connection.commitmethod, it will not commit any rows whose associatedSavepointhas been rolled back; it will commit all the other updated rows.
Releasing Savepoints
The methodConnection.releaseSavepointtakes aSavepointobject as a parameter and removes it from the current transaction.
After a savepoint has been released, attempting to reference it in a rollback operation causes aSQLExceptionto be thrown. Any savepoints that have been created in a transaction are automatically released and become invalid when the transaction is committed, or when the entire transaction is rolled back. Rolling a transaction back to a savepoint automatically releases and makes invalid any other savepoints that were created after the savepoint in question.
When to Call Method rollback
As mentioned earlier, calling the methodrollbackterminates a transaction and returns any values that were modified to their previous values. If you are trying to execute one or more statements in a transaction and get aSQLException, call the methodrollbackto end the transaction and start the transaction all over again. That is the only way to know what has been committed and what has not been committed. Catching aSQLExceptiontells you that something is wrong, but it does not tell you what was or was not committed. Because you cannot count on the fact that nothing was committed, calling the methodrollbackis the only way to be certain.
The methodCoffeesTable.updateCoffeeSalesdemonstrates a transaction and includes acatchblock that invokes the methodrollback. If the application continues and uses the results of the transaction, this call to therollbackmethod in thecatchblock prevents the use of possibly incorrect data.
Using RowSet Objects
A JDBCRowSetobject holds tabular data in a way that makes it more flexible and easier to use than a result set.
Oracle has defined fiveRowSetinterfaces for some of the more popular uses of aRowSet, and standard reference are available for theseRowSetinterfaces. In this tutorial you will learn how to use these reference implementations.
These versions of theRowSetinterface and their implementations have been provided as a convenience for programmers. Programmers are free to write their own versions of thejavax.sql.RowSetinterface, to extend the implementations of the fiveRowSetinterfaces, or to write their own implementations. However, many programmers will probably find that the standard reference implementations already fit their needs and will use them as is.
This section introduces you to theRowSetinterface and the following interfaces that extend this interface:
JdbcRowSetCachedRowSetWebRowSetJoinRowSetFilteredRowSet
What Can RowSet Objects Do?
AllRowSetobjects are derived from theResultSetinterface and therefore share its capabilities. What makes JDBCRowSetobjects special is that they add these new capabilities:
Function as JavaBeans Component
AllRowSetobjects are JavaBeans components. This means that they have the following:
- Properties
- JavaBeans Notification Mechanism
Properties
AllRowSetobjects have properties. A property is a field that has corresponding getter and setter methods. Properties are exposed to builder tools (such as those that come with the IDEs JDveloper and Eclipse) that enable you to visually manipulate beans. For more information, see the Properties lesson in the JavaBeans trail.
JavaBeans Notification Mechanism
RowSetobjects use the JavaBeans event model, in which registered components are notified when certain events occur. For allRowSetobjects, three events trigger notifications:
- A cursor movement
- The update, insertion, or deletion of a row
- A change to the entire
RowSetcontents
RowSetListenerinterface and have had themselves added to theRowSetobject's list of components to be notified when any of the three events occurs.
A listener could be a GUI component such as a bar graph. If the bar graph is tracking data in aRowSetobject, the listener would want to know the new data values whenever the data changed. The listener would therefore implement theRowSetListenermethods to define what it will do when a particular event occurs. Then the listener also must be added to theRowSetobject's list of listeners. The following line of code registers the bar graph componentbgwith theRowSetobjectrs.
Nowrs.addListener(bg);
bgwill be notified each time the cursor moves, a row is changed, or all ofrsgets new data.
Add Scrollability or Updatability
Some DBMSs do not support result sets that can be scrolled (scrollable), and some do not support result sets that can be updated (updatable). If a driver for that DBMS does not add the ability to scroll or update result sets, you can use aRowSetobject to do it. ARowSetobject is scrollable and updatable by default, so by populating aRowSetobject with the contents of a result set, you can effectively make the result set scrollable and updatable.
Kinds of RowSet Objects
ARowSetobject is considered either connected or disconnected. A connectedRowSetobject uses a JDBC driver to make a connection to a relational database and maintains that connection throughout its life span. A disconnectedRowSetobject makes a connection to a data source only to read in data from aResultSetobject or to write data back to the data source. After reading data from or writing data to its data source, theRowSetobject disconnects from it, thus becoming "disconnected." During much of its life span, a disconnectedRowSetobject has no connection to its data source and operates independently. The next two sections tell you what being connected or disconnected means in terms of what aRowSetobject can do.
Connected RowSet Objects
Only one of the standardRowSetimplementations is a connectedRowSetobject:JdbcRowSet. Always being connected to a database, aJdbcRowSetobject is most similar to aResultSetobject and is often used as a wrapper to make an otherwise non-scrollable and read-onlyResultSetobject scrollable and updatable.
As a JavaBeans component, aJdbcRowSetobject can be used, for example, in a GUI tool to select a JDBC driver. AJdbcRowSetobject can be used this way because it is effectively a wrapper for the driver that obtained its connection to the database.
Disconnected RowSet Objects
The other four implementations are disconnectedRowSetimplementations. DisconnectedRowSetobjects have all the capabilities of connectedRowSetobjects plus they have the additional capabilities available only to disconnectedRowSetobjects. For example, not having to maintain a connection to a data source makes disconnectedRowSetobjects far more lightweight than aJdbcRowSetobject or aResultSetobject. DisconnectedRowSetobjects are also serializable, and the combination of being both serializable and lightweight makes them ideal for sending data over a network. They can even be used for sending data to thin clients such as PDAs and mobile phones.
TheCachedRowSetinterface defines the basic capabilities available to all disconnectedRowSetobjects. The other three are extensions of theCachedRowSetinterface, which provide more specialized capabilities. The following information shows how they are related:
ACachedRowSetobject has all the capabilities of aJdbcRowSetobject plus it can also do the following:
- Obtain a connection to a data source and execute a query
- Read the data from the resulting
ResultSetobject and populate itself with that data - Manipulate data and make changes to data while it is disconnected
- Reconnect to the data source to write changes back to it
- Check for conflicts with the data source and resolve those conflicts
WebRowSetobject has all the capabilities of aCachedRowSetobject plus it can also do the following:
- Write itself as an XML document
- Read an XML document that describes a
WebRowSetobject
JoinRowSetobject has all the capabilities of aWebRowSetobject (and therefore also those of aCachedRowSetobject) plus it can also do the following:
- Form the equivalent of a
SQL JOINwithout having to connect to a data source
FilteredRowSetobject likewise has all the capabilities of aWebRowSetobject (and therefore also aCachedRowSetobject) plus it can also do the following:
- Apply filtering criteria so that only selected data is visible. This is equivalent to executing a query on a
RowSetobject without having to use a query language or connect to a data source.
Using JdbcRowSet Objects
AJdbcRowSetobject is an enhancedResultSetobject. It maintains a connection to its data source, just as aResultSetobject does. The big difference is that it has a set of properties and a listener notification mechanism that make it a JavaBeans component.
One of the main uses of aJdbcRowSetobject is to make aResultSetobject scrollable and updatable when it does not otherwise have those capabilities.
This section covers the following topics:
- Creating JdbcRowSet Objects
- Default JdbcRowSet Objects
- Setting Properties
- Using JdbcRowSet Objects
- Code Sample
Creating JdbcRowSet Objects
You can create aJdbcRowSetobject in various ways:
- By using the reference implementation constructor that takes a
ResultSetobject - By using the reference implementation constructor that takes a
Connectionobject - By using the reference implementation default constructor
- By using an instance of
RowSetFactory, which is created from the classRowSetProvider
JdbcRowSetimplementation of your JDBC driver. However, implementations of theRowSetinterface will differ from the reference implementation. These implementations will have different names and constructors. For example, the Oracle JDBC driver's implementation of theJdbcRowSetinterface is namedoracle.jdbc.rowset.OracleJDBCRowSet.
Passing ResultSet Objects
The simplest way to create aJdbcRowSetobject is to produce aResultSetobject and pass it to theJdbcRowSetImplconstructor. Doing this not only creates aJdbcRowSetobject but also populates it with the data in theResultSetobject.
Note: TheResultSetobject that is passed to theJdbcRowSetImplconstructor must be scrollable.
As an example, the following code fragment uses theConnectionobjectconto create aStatementobject,stmt, which then executes a query. The query produces theResultSetobjectrs, which is passed to the constructor to create a newJdbcRowSetobject initialized with the data inrs:
Astmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); rs = stmt.executeQuery("select * from COFFEES"); jdbcRs = new JdbcRowSetImpl(rs);JdbcRowSetobject created with aResultSetobject serves as a wrapper for theResultSetobject. Because theRowSetobjectrsis scrollable and updatable,jdbcRsis also scrollable and updatable. If you have run the methodcreateStatementwithout any arguments,rswould not be scrollable or updatable, and neither wouldjdbcRs.
Passing Connection Objects
The first statement in the following code excerpt fromJdbcRowSetSamplecreates aJdbcRowSetobject that connects to the database with theConnectionobjectcon:
The objectjdbcRs = new JdbcRowSetImpl(con); jdbcRs.setCommand("select * from COFFEES"); jdbcRs.execute();jdbcRscontains no data until you specify a SQL statement with the methodsetCommand, then run the methodexecute.
The objectjdbcRsis scrollable and updatable; by default,JdbcRowSetand all otherRowSetobjects are scrollable and updatable unless otherwise specified. See Default JdbcRowSet Objects for more information aboutJdbcRowSetproperties you can specify.
Using the Default Constructor
The first statement in the following code excerpt creates an emptyJdbcRowSetobject.
The objectpublic void createJdbcRowSet(String username, String password) { jdbcRs = new JdbcRowSetImpl(); jdbcRs.setCommand("select * from COFFEES"); jdbcRs.setUrl("jdbc:myDriver:myAttribute"); jdbcRs.setUsername(username); jdbcRs.setPassword(password); jdbcRs.execute(); // ... }jdbcRscontains no data until you specify a SQL statement with the methodsetCommand, specify how theJdbcResultSetobject connects the database, and then run the methodexecute.
All of the reference implementation constructors assign the default values for the properties listed in the section Default JdbcRowSet Objects.
Using the RowSetFactory Interface
With RowSet 1.1, which is part of Java SE 7 and later, you can use an instance ofRowSetFactoryto create aJdbcRowSetobject. For example, the following code excerpt uses an instance of theRowSetFactoryinterface to create theJdbcRowSetobject,jdbcRs:
The following statement creates thepublic void createJdbcRowSetWithRowSetFactory( String username, String password) throws SQLException { RowSetFactory myRowSetFactory = null; JdbcRowSet jdbcRs = null; ResultSet rs = null; Statement stmt = null; try { myRowSetFactory = RowSetProvider.newFactory(); jdbcRs = myRowSetFactory.createJdbcRowSet(); jdbcRs.setUrl("jdbc:myDriver:myAttribute"); jdbcRs.setUsername(username); jdbcRs.setPassword(password); jdbcRs.setCommand("select * from COFFEES"); jdbcRs.execute(); // ... } }RowSetProviderobjectmyRowSetFactorywith the defaultRowSetFactoryimplementation,com.sun.rowset.RowSetFactoryImpl:
Alternatively, if your JDBC driver has its ownmyRowSetFactory = RowSetProvider.newFactory();
RowSetFactoryimplementation, you may specify it as an argument of thenewFactorymethod.
The following statements create theJdbcRowSetobjectjdbcRsand configure its database connection properties:
ThejdbcRs = myRowSetFactory.createJdbcRowSet(); jdbcRs.setUrl("jdbc:myDriver:myAttribute"); jdbcRs.setUsername(username); jdbcRs.setPassword(password);RowSetFactoryinterface contains methods to create the different types ofRowSetimplementations available in RowSet 1.1 and later:
createCachedRowSetcreateFilteredRowSetcreateJdbcRowSetcreateJoinRowSetcreateWebRowSet
Default JdbcRowSet Objects
When you create aJdbcRowSetobject with the default constructor, the newJdbcRowSetobject will have the following properties:
type:ResultSet.TYPE_SCROLL_INSENSITIVE(has a scrollable cursor)concurrency:ResultSet.CONCUR_UPDATABLE(can be updated)escapeProcessing:true(the driver will do escape processing; when escape processing is enabled, the driver will scan for any escape syntax and translate it into code that the particular database understands)maxRows:0(no limit on the number of rows)maxFieldSize:0(no limit on the number of bytes for a column value; applies only to columns that storeBINARY,VARBINARY,LONGVARBINARY,CHAR,VARCHAR, andLONGVARCHARvalues)queryTimeout:0(has no time limit for how long it takes to execute a query)showDeleted:false(deleted rows are not visible)transactionIsolation:Connection.TRANSACTION_READ_COMMITTED(reads only data that has been committed)typeMap:null(the type map associated with aConnectionobject used by thisRowSetobject isnull)
JdbcRowSetand all otherRowSetobjects are scrollable and updatable unless you set different values for those properties.
Setting Properties
The section Default JdbcRowSet Objects lists the properties that are set by default when a newJdbcRowSetobject is created. If you use the default constructor, you must set some additional properties before you can populate your newJdbcRowSetobject with data.
In order to get its data, aJdbcRowSetobject first needs to connect to a database. The following four properties hold information used in obtaining a connection to a database.
username: the name a user supplies to a database as part of gaining accesspassword: the user's database passwordurl: the JDBC URL for the database to which the user wants to connectdatasourceName: the name used to retrieve aDataSourceobject that has been registered with a JNDI naming service
DataSourceobject, but it may not be practical for you to register aDataSourceobject with a JNDI naming service, which is generally done by a system administrator. Therefore, the code examples all use theDriverManagermechanism to obtain a connection, for which you use theurlproperty and not thedatasourceNameproperty.
Another property that you must set is thecommandproperty. This property is the query that determines what data theJdbcRowSetobject will hold. For example, the following line of code sets thecommandproperty with a query that produces aResultSetobject containing all the data in the tableCOFFEES:
After you have set thejdbcRs.setCommand("select * from COFFEES");commandproperty and the properties necessary for making a connection, you are ready to populate thejdbcRsobject with data by calling theexecutemethod.
ThejdbcRs.execute();
executemethod does many things for you in the background:
- It makes a connection to the database using the values you assigned to the
url,username, andpasswordproperties. - It executes the query you set in the
commandproperty. - It reads the data from the resulting
ResultSetobject into thejdbcRsobject.
Using JdbcRowSet Objects
You update, insert, and delete a row in aJdbcRowSetobject the same way you update, insert, and delete a row in an updatableResultSetobject. Similarly, you navigate aJdbcRowSetobject the same way you navigate a scrollableResultSetobject.
The Coffee Break chain of coffee houses acquired another chain of coffee houses and now has a legacy database that does not support scrolling or updating of a result set. In other words, anyResultSetobject produced by this legacy database does not have a scrollable cursor, and the data in it cannot be modified. However, by creating aJdbcRowSetobject populated with the data from aResultSetobject, you can, in effect, make theResultSetobject scrollable and updatable.
As mentioned previously, aJdbcRowSetobject is by default scrollable and updatable. Because its contents are identical to those in aResultSetobject, operating on theJdbcRowSetobject is equivalent to operating on theResultSetobject itself. And because aJdbcRowSetobject has an ongoing connection to the database, changes it makes to its own data are also made to the data in the database.
This section covers the following topics:
Navigating JdbcRowSet Objects
AResultSetobject that is not scrollable can use only thenextmethod to move its cursor forward, and it can move the cursor only forward from the first row to the last row. A defaultJdbcRowSetobject, however, can use all of the cursor movement methods defined in theResultSetinterface.
AJdbcRowSetobject can call the methodnext, and it can also call any of the otherResultSetcursor movement methods. For example, the following lines of code move the cursor to the fourth row in thejdbcRsobject and then back to the third row:
The methodjdbcRs.absolute(4); jdbcRs.previous();
previousis analogous to the methodnextin that it can be used in awhileloop to traverse all of the rows in order. The difference is that you must move the cursor to a position after the last row, andpreviousmoves the cursor toward the beginning.
Updating Column Values
You update data in aJdbcRowSetobject the same way you update data in aResultSetobject.
Assume that the Coffee Break owner wants to raise the price for a pound of Espresso coffee. If the owner knows that Espresso is in the third row of thejdbcRsobject, the code for doing this might look like the following:
The code moves the cursor to the third row and changes the value for the columnjdbcRs.absolute(3); jdbcRs.updateFloat("PRICE", 10.99f); jdbcRs.updateRow();PRICEto 10.99, and then updates the database with the new price.
Calling the methodupdateRowupdates the database becausejdbcRshas maintained its connection to the database. For disconnectedRowSetobjects, the situation is different.
Inserting Rows
If the owner of the Coffee Break chain wants to add one or more coffees to what he offers, the owner will need to add one row to theCOFFEEStable for each new coffee, as is done in the following code fragment fromJdbcRowSetSample. Notice that because thejdbcRsobject is always connected to the database, inserting a row into aJdbcRowSetobject is the same as inserting a row into aResultSetobject: You move to the cursor to the insert row, use the appropriate updater method to set a value for each column, and call the methodinsertRow:
When you call the methodjdbcRs.moveToInsertRow(); jdbcRs.updateString("COF_NAME", "HouseBlend"); jdbcRs.updateInt("SUP_ID", 49); jdbcRs.updateFloat("PRICE", 7.99f); jdbcRs.updateInt("SALES", 0); jdbcRs.updateInt("TOTAL", 0); jdbcRs.insertRow(); jdbcRs.moveToInsertRow(); jdbcRs.updateString("COF_NAME", "HouseDecaf"); jdbcRs.updateInt("SUP_ID", 49); jdbcRs.updateFloat("PRICE", 8.99f); jdbcRs.updateInt("SALES", 0); jdbcRs.updateInt("TOTAL", 0); jdbcRs.insertRow();insertRow, the new row is inserted into thejdbcRsobject and is also inserted into the database. The preceding code fragment goes through this process twice, so two new rows are inserted into thejdbcRsobject and the database.
Deleting Rows
As is true with updating data and inserting a new row, deleting a row is just the same for aJdbcRowSetobject as for aResultSetobject. The owner wants to discontinue selling French Roast decaffeinated coffee, which is the last row in thejdbcRsobject. In the following lines of code, the first line moves the cursor to the last row, and the second line deletes the last row from thejdbcRsobject and from the database:
jdbcRs.last(); jdbcRs.deleteRow();
Code Sample
The sampleJdbcRowSetSampledoes the following:
- Creates a new
JdbcRowSetobject initialized with theResultSetobject that was produced by the execution of a query that retrieves all the rows in theCOFFEEStable - Moves the cursor to the third row of the
COFFEEStable and updates thePRICEcolumn in that row - Inserts two new rows, one for
HouseBlendand one forHouseDecaf - Moves the cursor to the last row and deletes it
ReplyDeletenice article for beginners.thank you.
jdbc tutorial