|
Note: The JDBC 2.0 API includes many new features in the
Readers who are new to JDBC might want to refer to the JDBC Basics chapter in the online version of The Java Tutorial Continued. For further training on JDBC, take a look at Chapter 3 which focuses on this API's new features and how to set up distributed application connections.
|
As the conceptual description of rowsets pointed out, what you can do with a
rowset depends on how it has been implemented. It can also depend on which
properties have been set. The example rowsets used in this chapter are based on
the CachedRowSet implementation, but because they are used
for different purposes, one has several properties set whereas the other has none.
Among other things, this tutorial will show you which properties to use and when
to use them.
Getting back to our owner of The Coffee Break chain, he has had one of his developers write an application that lets him project the effects of changing different coffee prices. To create this application, the developer hooked together various JavaBeans components, setting their properties to customize them for his application. The first JavaBeans component, called Projector, was one that the owner bought from an economic forecasting firm. This Bean takes all kinds of factors into account to project future revenues. Given the price and past sales performance of a coffee, it predicts the revenue the coffee is likely to generate and displays the results as a bar chart.
The second JavaBeans component is a CachedRowSet. The owner
wants to be able to look at different coffee pricing scenarios using his laptop, so the
application is set up such that it creates a rowset that can be copied to the laptop's
disc. The owner can later fire up the application on his laptop so that he can make
updates to the rowset to test out various pricing strategies.
The third Bean is a form for displaying and updating ResultSet objects.
The form can be used for displaying and updating our CachedRowSet
because CachedRowSet is simply a specialized implementation of
ResultSet.
The application has a graphical user interface that includes buttons for opening and closing the application. These buttons are themselves JavaBeans components that the programmer assembled to make the GUI for his application.
While he is at
work, the owner can click on the form's New Data button to get a
rowset filled with data. This is the work that requires the rowset to get a connection
to the data source, execute its query, get a result set, and populate itself with the
result set data. When this work is done, the rowset disconnects itself. The owner can
now click on the Close button to save the disconnected rowset to
his laptop's disc. At home or on a plane, the owner can open the application on his
laptop and click the button Open to copy the rowset from disc and
start making updates using the form. The form displays the rowset, and he simply
uses arrow keys or tabs to highlight the piece of data he wants to update. He uses
the editing component of the form to type in new values, and the Projector Bean
shows the effects of the new values in its bar chart. When he gets back to headquarters,
the owner can copy his updated rowset to his office computer if he wants to
propagate the updates back to the database.
As part of the implementation, the application programmer will do the following:
CachedRowSet Bean and set its properties
To put this all together, the application programmer will probably use a visual Bean
development tool, which means that he will use very little RowSet
API directly. Of course, the owner will use the application without writing any
RowSet code himself. The upshot of all of this is that generally tools
will generate the RowSet code you see in this tutorial. Also, remember
that the code shown here is for illustrative purposes only because it uses the
CachedRowSet class, for which there is no implementation currently
available. Although the JDBC Standard Extension specification gives a preliminary
outline of its functionality, some details in its implementation may be different when
it is completed.
Because a programmer will generally use a Bean visual development tool to create
a RowSet object and set its properties, the example code fragments
shown here would most likely be executed by a Bean development tool. The main
purpose of this section is to show you when and why you would want to set certain
properties.
The code for creating a CachedRowSet object simply uses the
default constructor.
CachedRowSet crset = new CachedRowSet();
Now the programmer can set the CachedRowSet object's
properties to suit the owner's needs. The RowSet interface,
which the CachedRowSet class implements, contains
get/set methods for retrieving and setting
properties. These properties and a RowSet object's event
notification capabilities (explained in a later section) are what make a
RowSet object a JavaBeans component. A
rowset's properties include its command, concurrency, type, data source name,
url, user name, password, transaction isolation level, escape processing, maximum
field size, maximum rows, query timeout, and type map. You are required to set
only those properties that are needed for your particular use of a rowset. The
following example uses several properties and explains why they are needed.
The owner wants the convenience of being able to make updates by scrolling to the
rows he wants to update, so the property for the type needs to be set to scrollable. It
may be that the CachedRowSet class will be implemented so that
it is by default TYPE_SCROLL_INSENSITIVE, in which case the
programmer would not need to set the rowset's type property. It does no harm to
set it, however. The default for the concurrency property is
ResultSet.CONCUR_READ_ONLY, so it needs to be set to
CONCUR_UPDATABLE for our entrepreneur's use. The following
lines of code make the CachedRowSet object
crset scrollable and updatable.
crset.setType(ResultSet.TYPE_SCROLL_INSENSITIVE); crset.setConcurrency(ResultSet.CONCUR_UPDATABLE);
The owner will want to make updates to the table COFFEES, so the
programmer sets the rowset's command string with the query SELECT * FROM
COFFEES. When the method execute is called, this command
will be executed, and the rowset will be populated with the data in the table
COFFEES. The owner can then use the rowset to make his updates.
In order to execute its command, the rowset will need to make a connection with the
database COFFEEBREAK, so the programmer also needs to set the
properties required for that. If the DriverManager were being used
to make a connection, he would set the properties for a JDBC URL, a user name, and
a password. However, he wants to use the preferred means of getting a connection,
which is to use a DataSource object, so he will set the properties for
the data source name, the owner's user name, and the owner's password. For security,
the programmer may opt to get the user name and password from the owner
interactively at run time, in which case he would not need to set them as properties.
(If you need a refresher on using a DataSource object to make a
connection, start reading at page 160 in the advanced tutorial.) Here is the code a
tool would generate to set the command string, the data source name, the user
name, and the password properties for the CachedRowSet object
crset.
crset.setCommand("SELECT * FROM COFFEES");
crset.setDataSourceName("jdbc/coffeesDB");
crset.setUsername("juanvaldez");
crset.setPassword("espresso");
Note that the String object set for the data source name is the
logical name that the system administrator (or someone acting in that capacity)
registered with a JNDI naming service as the logical name for the
COFFEEBREAK database. A programmer just needs to get the
logical name, in this case jdbc/coffeesDB, from the system
administrator and use it to set the data source property. When the rowset makes
a connection, it will use the information in its properties, so the programmer or
tool will not need to do anything except execute the command string, which you
will see later. Internally the rowset gives the JNDI naming service the string the
programmer set for the data source name property. Because
jdbc/coffeesDB was previously bound to a
DataSource object representing the database
COFFEEBREAK, the naming service will return a
DataSource object that the rowset can use to get a connection to
COFFEEBREAK.
The programmer sets one more property, the transaction isolation level, which
determines the transaction isolation level given to the connection that the rowset
establishes. The owner does not want to read any data that has not been committed,
so the programmer chooses the level TRANSACTION_READ_COMMITTED.
The following line of code sets the rowset's property so that "dirty reads"
will not be allowed.
crset.setTransactionIsolation(
Connection.TRANSACTION_READ_COMMITTED);
The other properties are all optional for the owner, so the programmer does not set
any others. For example, he does not need to set a type map because there are no
custom mappings in the table COFFEES. If the owner has the
programmer change the command string so that it gets data from a table that has
user-defined types with custom mappings, then the type map property will need to
be set.
Being a JavaBeans component, a RowSet object has the ability to
participate in event notification. In the application we are considering, the Projector
Bean needs to be notified when the rowset is updated, so it needs to be registered
with the rowset as a listener. The developer who wrote the Projector Bean will already
have implemented the three RowSetListener methods
rowChanged, rowSetChanged, and
cursorMoved. These methods specify what the listener will do when
an event occurs on the CachedRowSet object
crset. The Projector Bean does not care about where
the cursor is, so its implementation of cursorMoved is probably to
do nothing. When one or more values in a row changes, the method
rowChanged will be called on the listener. This method will
probably check to see if the value in the PRICE or
SALES columns has changed, and if either has changed, it
will plug the appropriate value(s) into its projection model to get a value to display.
The method rowSetChanged is invoked when the contents of the
whole rowset change, which happens only when the rowset's command string has
been changed and then executed. This method will probably have an implementation
similar to that of the method rowChanged.
The following line of code registers projector, the bar
chart component, as a listener for crset.
crset.addRowSetListener(projector);
Now that projector is registered as a listener with
the rowset, it will be notified every time an event occurs on
crset.
The programmer will use his development tool to bring one more component
into the application, the editor that will be used to update the rowset. He does
this by simply specifying crset as a property on
the editor. This tells the editor to which component it should send the changes
keyed into it.
So far the programmer has created a CachedRowSet object and
set its properties. Now all he has to do in order to get a scrollable and updatable
rowset is to call the method execute on the rowset. As a result of
this call, the rowset does all of the following behind the scenes:
COFFEEBREAK database,
using the properties for the data source name, the user name, and the
password
SELECT * FROM COFFEES, which
has been set as its command string property
The invocation that accomplishes all of this is the following single line of code.
crset.execute();
This produces a CachedRowSet object that contains the same
data as the ResultSet object generated by the query
SELECT * FROM COFFEES. In other words, they both
contain the data in the table COFFEES. The difference is that
because the application developer has set the properties on the rowset to make
it scrollable and updatable, the owner can move the cursor to any position in
the rowset and modify its data. This is true even if the ResultSet
object from which the rowset got its data is not scrollable or updatable. In fact,
it is especially when a JDBC driver does not support scrollable or updatable result
sets that you might want to use a rowset as a means of getting those capabilities.
Scrolling in a rowset is exactly the same as scrolling in a result set. The cursor is
positioned before the first row in a newly-populated rowset, and a call to the method
next moves the cursor to the first row. Thus, to iterate through a
rowset from first row to last row, you call the method next in a
while loop, just as you would do for a ResultSet
object. For example, the following code fragment iterates through the entire
RowSet object crset, printing the
name of every coffee in the table COFFEES.
crset.execute();
while (crset.next()) {
System.out.println(crset.getString("COF_NAME"));
}
With a non-scrollable rowset or result set, you are limited to iterating through the data once and in a forward direction. With scrolling, you can move the cursor in any direction and can go to a row as many times as you like. If you want a review of how to move the cursor, see the advanced tutorial section "Moving the Cursor in Scrollable Result Sets" on page 109.
The owner of The Coffee Break wanted a scrolling rowset so that he could easily make updates to a particular row. The following section illustrates moving the cursor to update a row.
Updating a CachedRowSet object is similar to updating a
ResultSet object. The updateXXX methods and
the methods insertRow and deleteRow are inherited
from ResultSet and are used in the same way. For example, the owner
has brought up the rowset, which contains the current data in the table
COFFEES, on his laptop computer. He wants to change the price for
French_Roast_Decaf, which is in the fifth row, so he moves the cursor
there. The GUI tool displaying the rowset will generate the following line of code to
move the cursor to the fifth row.
crset.absolute(5);
The Projector Bean will be notified that the cursor has moved but will do nothing about it.
The owner now moves the cursor to the price, which is the third column, and changes the column's value to 10.49. In response, the GUI tool generates the following update statement.
crset.updateFloat(3, 10.49f);
Next the owner clicks on the ROW DONE button to indicate that
he is finished making updates to the current row. This causes the GUI tool to
generate the following line of code.
crset.updateRow();
The method rowChanged is called on the Projector Bean to notify
it that a row in the rowset has changed. The Projector Bean determines whether the
price and/or number of pounds sold has changed and, if so, plugs the most current
price and number of pounds sold into its projection calculations. After it arrives at
new projected values for revenue from sales of the affected coffee, it updates the
bar chart to reflect the new values.
Now the owner moves to the previous row, which is the fourth row, changes the price
to 9.49 and the sales amount to 500, and clicks the ROW DONE button.
Note that the fourth column in the rowset contains the number of pounds sold in the
last week. The GUI tool generates the following code.
crset.previous(-1); // or crset.absolute(4); crset.updateFloat(3, 9.49f); crset.updateInt(4, 500); crset.updateRow();
So far the owner has updated the fourth and fifth rows in the rowset, but he has not
updated the values in the database. If this had been a ResultSet
object, both the result set and the database would have been updated with the call
to the method updateRow. However, because this is a disconnected
rowset, the method CachedRowSet.acceptChanges has to be called
for the database to be updated. The owner will click the UPDATE
DATABASE button if he wants to propagate his changes back to the database.
The GUI tool will generate the following line of code.
crset.acceptChanges();
The application is implemented so that the acceptChanges method
is not actually invoked until the owner returns to work and copies the updated
rowset to his office computer. On the office machine, the rowset can create a
connection for writing updated values back to the database. In addition to updating
the database with the new values in rows four and five of the rowset, the
acceptChanges method will set the values that the rowset keeps as
its "original" values. Original values are the values the rowset had just
before the current set of updates.
Before writing new values to the database, the rowset's writer component works
behind the scenes to compare the rowset's original values with those in the database.
If no one has changed values in the table, the rowset's original values and the values
in the database should be the same. If there is no conflict, that is, the rowset's original
values and the database's values match, the writer may choose to write the updated
values to the database, depending on how it is implemented. The current values that
the writer enters will be used as the original values when a new set of updates is made.
For example, the price in the fifth row was 9.99 before it was updated to 10.49. The
rowset's original price of 9.99 should match the price for French_Roast_Decaf
coffee in the database. If it does, the writer can update the database price to 10.49 and
change the rowset's original price to 10.49. The next time the price for
French_Roast_Decaf is changed, the writer will compare the original
value (10.49) with the current value in the database.
In this example scenario you have seen how a rowset can be used to pass a set of rows to a thin client, in this case a laptop computer. You have also seen how a rowset can provide scrolling and updatability, which the JDBC driver used at the owner's office does not support. The next part of this chapter will show you how a rowset might be used in an EJB application.
For this example, we assume that you are familiar with the concepts discussed in "Basic Tutorial" and "Advanced Tutorial," especially the sections on using the JDBC Standard Extension API. This EJB example gives only a high-level explanation of the EJB classes and interfaces used in it; if you want a more thorough explanation, you should see the EJB specification available at the following URL:
http://java.sun.com/products/ejb
Let's assume that the owner of The Coffee Break has set up an EJB application to make it easier for his managers to order coffee for their stores. The managers can bring up an order form that has two buttons: one button for viewing a table with the coffees and prices currently available and another button for placing an order.
The developer who designed the form used JavaBeans components to create the
buttons, layout, and other components of the form. The application developer will
use Enterprise JavaBeans components to make the buttons do their work. The
EJB component (enterprise Bean) will be deployed in a container provided by an EJB
server. The container manages the life cycle of its enterprise Beans and also manages
the boundaries of the transactions in which they participate. The EJB implementation
also includes a DataSource class that works with an
XADataSource class to provide distributed transactions.
An EJB application is always a distributed application, an application that distributes its work among different machines. An EJB application uses the three-tier model. The first tier is the client, which is typically a web browser. In our example, the client is a form running on The Coffee Break's intranet. The second tier, or middle tier, is made up of the EJB server and the JDBC driver. The third tier is one or more database servers.
The method getCoffees is one of the three methods that is
implemented by our enterprise Bean, the class CoffeesBean. Let's
look at the implementation of this method, which creates and populates a rowset,
and then look at how its invocation and execution are spread out over the three tiers.
This method will be explained in more detail later in this chapter.
public RowSet getCoffees() throws SQLException {
Connection con = null;
try {
con = ds.getConnection("managerID", "mgrPassword");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT COF_NAME, PRICE FROM COFFEES");
CachedRowSet rset = new CachedRowSet();
crset.populate(rs);
rs.close();
stmt.close();
return crset;
}
finally {
if (con != null) con.close();
}
return null;
}
|
getCoffeesDisplay Coffee Prices on
The Coffee Break order form.
getCoffees getCoffeesNote that a distributed application is not restricted to three tiers: It may have two tiers, a client and server. Also note that a distributed application is different from a distributed transaction. A distributed transaction, often referred to as a global transaction, is a transaction that involves two or more DBMS servers. A global transaction will always occur within the context of a distributed application because by definition it requires at least a client and two servers.
As you have just seen, the rowset used in our EJB example is a
CachedRowSet object that is created and populated on the
middle tier server. This disconnected rowset is then sent to a thin client. All of
this is also true of the rowset in the laptop example, but there are some differences
between the two rowsets. The main difference is that the rowset used in the order
form for The Coffee Break is not updatable by the client; it is simply a list of coffees
and their prices that the manager can look at. Therefore, the rowset does not need
its concurrency property set. In fact, the rowset used in the order form does not
need any properties at all set. The method getCoffees gets a
DataSource object and then uses it to get a connection, so the
rowset does not need to perform these tasks. This means that the rowset does not
use a data source name, user name, or password and thus does not need the
properties for them set. The order form rowset also needs no command property
because the getCoffees implementation executes the query to
get coffee names and prices. Recall that by contrast, the rowset in the laptop
example created a connection, executed its command string, and populated itself
with data by having its execute method invoked. The only
CachedRowSet method used in the EJB example is
populate, which just reads data from the ResultSet
object passed to it and inserts the data into the rowset.
In the EJB framework, one or more enterprise Beans can be deployed in a container, which manages the Beans. The container controls the life cycle of a Bean, and it also controls the boundaries of distributed transactions.
Every enterprise Bean has a transaction attribute to tell the container how
it should be managed with regard to distributed transactions. The developer
of the enterprise Bean in our example has given the Bean the transaction
attribute TX_REQUIRED, which means that the Bean's methods must
be executed in the scope of a global transaction. If the component that invokes
one of the enterprise Bean's methods is already associated with a global
transaction, the enterprise Bean method will be associated with that
transaction. If not, the container must start a new distributed transaction
and execute the enterprise Bean method in the scope of that transaction. When
the method has completed, the container will commit the transaction.
The fact that the Bean's container manages the start and end of transactions has
implications for the Bean's behavior. First, the Bean should not call the methods
commit or rollback. Second, the Bean should
not change the default setting for a connection's auto-commit mode. Because the
DataSource object is implemented to work with distributed
transactions, any connection it produces has its auto-commit mode disabled. This
prevents the connection from automatically committing a transaction, which would
get in the way of the container's management of the transaction. Thus, the Bean
should leave the connection's auto-commit mode disabled in addition to not calling
the methods commit or rollback.
SessionBean Object
The EJB component in this example is a stateless SessionBean
object, which is the simplest kind of enterprise Bean. Being a session Bean means
that it is an extension of the client that creates it, typically reading and updating data
in a database on behalf of the client. A session Bean is created when a client begins
its session and is closed when the client ends its session. Being stateless means that
the Bean does not need to retain any information it might get from a client from one
method invocation to the next. Therefore, any Bean instance can be used for any
client.
For example, the enterprise Bean we will use has three methods. One creates a
CoffeesBean object, another one retrieves a table of coffees and
prices, and a third places a manager's order. In general, because our enterprise Bean
is a SessionBean object, it is created when a manager opens The
Coffee Break order form and is closed when he/she quits it. It is stateless because it
does not have to remember coffee prices or what the client ordered.
An EJB application has four parts, which are described briefly in the following list. The enterprise Bean developer writes the first three, and anyone, including the enterprise Bean developer, may supply the fourth. The sections following this one show the code for each interface or class.
In our example, the remote interface is the interface Coffees,
which declares the methods getCoffees and
placeOrder. The container generates an implemention of
this interface that delegates to the class CoffeesBean.
CoffeesBean, supplied by the developer, actually defines
what the methods do. It is the third item in this list. Instances of the
interface Coffees are EJBObjects.
Coffees object
In our example, the home interface is the interface
CoffeesHome, which is registered with a JNDI naming
service. It declares the method create and creates
Coffees objects. The container implements this interface
so that the method create delegates its work to the
method ejbCreate, which is implemented by the
CoffeesBean class.
Instances of this class are enterprise Beans. In our example this class
is CoffeesBean, which implements the methods
ejbCreate, getCoffees, and
placeOrder.
In our example, the client class is CoffeesClient. This class
typically includes GUI components. For our example, if it were fully implemented,
the CoffeesClient class would include buttons for invoking the
methods getCoffees and placeOrder. It would
also include a text editor for typing in the parameters for the method
placeOrder. This class could have many different
implementations, with or without GUI components, and it could be
written by the enterprise Bean developer or anyone else.
Now let's look at some sample code for an EJB application. Note that we kept this example very simple in order to concentrate on the basic concepts.
The interface Coffees declares the methods that managers of The
Coffee Break coffee houses can invoke. In other words, this interface contains the
methods that a remote client can invoke.
This interface, which extends EJBObject, declares the methods
getCoffees and placeOrder. It imports four
packages because it uses elements from each one. Both methods can throw a
RemoteException as well as an SQLException
because they use methods from the package java.rmi, the package
for remote method invocation on Java objects. The following code defines the
interface Coffees.
import java.rmi.*;
import java.sql.*;
import javax.sql.*;
import javax.ejb.*;
public interface Coffees extends EJBObject {
public RowSet getCoffees() throws RemoteException,
SQLException;
public void placeOrder(String cofName,
int quantity,
String MgrId)
throws RemoteException, SQLException;
}
|
The home interface CoffeesHome is a factory for
Coffees objects. It declares only the single method
create, which creates Coffees objects, thus
making CoffeesHome the simplest possible form of the home
interface. The method create may throw a RemoteException,
from the java.rmi package, or a CreateException,
from the javax.ejb package.
import java.rmi.*;
import javax.ejb.*;
public interface CoffeesHome extends javax.ejb.EJBHome {
public Coffees create() throws RemoteException,
CreateException;
}
|
So far you have seen two interfaces with one thing in common: These interfaces
contain the methods that will be called by the client class. The two methods in
Coffees are called in response to button clicks from a manager.
The client calls the CoffesHome.create method to get a
Coffees object it can use for invoking the methods defined on
Coffees.
The first thing the CoffeesClient class does is to retrieve a
CoffeesHome object that has been registered with a JNDI naming
service. The CoffeesHome object has been bound to the logical
name ejb/Coffees, so when ejb/Coffees is given
to the method lookup, it returns a CoffeesHome
object. Because the instance of CoffeesHome is returned as an RMI
PortableRemoteObject, it has to be narrowed to a
CoffeesHome object before being assigned to the variable
chome. The method CoffeesHome.create
can then be called on chome to create the
Coffees object coffees. Once the
client has a Coffees object, it can call the methods
Coffees.getCoffees and Coffees.placeOrder on it.
The methods invoked by a CoffeesClient object are implemented
in the class CoffeesBean, which you will see next.
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import javax.ejb.*;
import javax.rmi.*;
class CoffeesClient {
public static void main(String[] args) {
try {
Context ctx = new InitialContext();
Object obj = ctx.lookup("ejb/Coffees");
CoffeesHome chome = (CoffeesHome)
PortableRemoteObject.narrow(
obj, CoffeesHome.class);
Coffees coffees = chome.create();
RowSet rset = coffees.getCoffees();
// display the coffees for sale
// get user input from GUI
coffees.placeOrder("Colombian", 3, "12345");
// repeat until user quits
}
catch (Exception e) {
System.out.print(e.getClass().getName() + ":");
System.out.println(e.getMessage());
}
}
}
|
The final part of our EJB component is the class CoffeesBean,
which implements the methods that are declared in the interfaces
Coffees and CoffeesHome and that are invoked
in the class CoffeesClient. Note that it implements the
SessionBean interface, but because it is a stateless
SessionBean object, the implementations of the methods
ejbRemove, ejbPassivate, and
ejbActivate are empty. These methods apply to a
SessionBean object with conversational state, but not to a
stateless SessionBean object such as an instance of
CoffeesBean. We will examine the
code more closely after you have looked at it.
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import javax.ejb.*;
public class CoffeesBean implements SessionBean {
public CoffeesBean () {}
public void ejbCreate() throws CreateException {
try {
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/CoffeesDB");
}
catch (Exception e) {
throw new CreateException();
}
}
public RowSet getCoffees() throws SQLException {
Connection con = null;
try {
con = ds.getConnection("managerID", "mgrPassword");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
"select * from coffees");
CachedRowSet rset = new CachedRowSet();
rset.populate(rs);
rs.close();
stmt.close();
return rset;
} finally {
if (con != null) con.close();
}
}
public void placeOrder(String cofName,
int quantity,
String MgrId)
throws SQLException {
Connection con = null;
try {
con = ds.getConnection("managerID", "mgrPassword");
PreparedStatement pstmt = con.prepareStatement(
"insert into orders values (?, ?,?)");
pstmt.setString(1, cofName);
pstmt.setInt(2, quantity);
pstmt.setString(3, MgrId);
pstmt.executeUpdate();
pstmt.close();
} finally {
if (con != null) con.close();
}
}
//
// Methods inherited from SessionBean
//
public void setSessionContext(SessionContext sc) {
this.sc = sc;
}
public void ejbRemove() {}
public void ejbPassivate() {}
public void ejbActivate() {}
private SessionContext sc = null;
private Context ctx = null;
private DataSource ds = null;
}
|
CoffeesBean can be divided into the following steps:
public CoffeesBean() {}
ejbCreate
public void ejbCreate() throws CreateException {
try {
ctx = new InitialContext();
ds = (DataSource)ctx.lookup(""jdbc/CoffeesDB");
}
catch (Exception e) {
throw new CreateException();
}
}
|
The Context object ctx and the
DataSource object ds are private
fields originally set to null. This method retrieves an
instance of the DataSource class that is associated with
the logical name jdbc/CoffeesDB and assigns it to
ds. The Data- Source object
ds can be used to create connections to the
database COFFEEBREAK. This work is done once when the
Bean is created to avoid doing it over and over each time the methods
getCoffees and placeOrder are called.
getCoffees. We will break this
method down into smaller units for examination.
public RowSet getCoffees() throws SQLException {
Connection con = null;
try {
con = ds.getConnection("managerID", "mgrPassword");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT COF_NAME, PRICE FROM COFFEES");
|
As the signature indicates, this method returns a RowSet object. It
uses the DataSource object that the method ejbCreate
obtained from the JNDI naming service to create a connection to the database that
ds represents. Supplying a user name and password to
the method DataSource.getConnection produces the
Connection object con. This is a
connection to the database COFFEEBREAK because when the
system administrator deployed the DataSource object used to
make the connection, she gave it the properties for the COFFEEBREAK
database.
The code then creates a Statement object and uses it to execute a
query. The query produces a ResultSet object that has the name and
price for every coffee in the table COFFEES. This is the data that the
client has requested.
CachedRowSet crset = new CachedRowSet(); crset.populate(rs);
The preceding code creates the CachedRowSet object
crset and populates it with the data that is in
rs. This code assumes that the class
CachedRowSet has been defined and that it provides the method
populate, which reads data from a ResultSet
object and inserts it into a RowSet object. Now let's look at the
rest of the implementation of the getCoffees method.
rs.close();
stmt.close();
return rset;
}
finally {
if (con != null) con.close();
}
return null;
|
The method getCoffees returns the newly-populated
RowSet object if the connection is made and the rowset is
successfully filled with data. Otherwise, getCoffees returns
null. There are two points to be made about these lines of code.
First, it contains a finally block that assures that even if there is an
exception thrown, if the connection is not null, it will be closed
and thereby recycled. Because the EJB server and JDBC driver being used implement
connection pooling, a valid connection will automatically be put back into the pool
of available connections when it is closed.
The second point is that the code does not enable the auto-commit mode, nor does
it call the methods commit or rollback. The reason
is that this enterprise Bean is operating within the scope of a distributed transaction,
so the container will commit or rollback all transactions.
placeOrder public void placeOrder(String cofName,
int quantity,
String MgrId)
throws SQLException {
Connection con = null;
try {
con = ds.getConnection("managerID", "mgrPassword");
PreparedStatement pstmt = con.prepareStatement(
"INSERT INTO ORDERS VALUES (?, ?, ?");
pstmt.setString(1, cofName);
pstmt.setInt(2, quantity);
pstmt.setString(3, MgrId);
pstmt.executeUpdate();
pstmt.close();
}
catch (SQLException e) {
throw e;
}
finally {
if (con != null) con.close();
}
}
|
The method placeOrder gets values for the three input parameters
and then sets them. After the manager clicks the PLACE ORDER
button on the order form, he gets three blank spaces into which to type the
coffee name, the number of pounds, and his ManagerID. For example, the
manager might have typed "Colombian", 50, and
"12345" in the blanks on his form. The server would get the
following line of code:
coffees.placeOrder("Colombian", 50, "12345");
The placeOrders method would produce the following code:
pstmt.setString(1, "Colombian"); pstmt.setInt(2, 50); pstmt.setString(3, "12345");
The following update statement would effectively be sent to the DBMS server to be executed.
INSERT INTO ORDERS VALUES ("Colombian", 50, "12345")
This would put a new row into the ORDERS table to record the
manager's order.
As with the method getCoffees, the placeOrders
method has a finally block to make sure that a valid connection is
closed even if there is an exception thrown. This means that if the connection is valid,
it will be returned to the connection pool to be reused.
The rest of the implementation deals with methods inherited from the
SessionBean interface. The methods ejbRemove,
ejbPassivate, and ejbActivate apply to
SessionBean objects with state. Because CoffeesBean
is a stateless SessionBean object, the implementations for these
methods are empty.
Congratulations! You have finished the tutorials on the complete JDBC API. You
can create tables, update tables, retrieve and process data from result sets, use
prepared statements and stored procedures, and use transactions. You can also
use the more advanced functionality, including SQL3 data types, batch updates,
programmatic updates, custom mapping, making a connection with a
DataSource object, connection pooling, distributed transactions,
and rowsets. You have also seen how the JDBC API works with EJB technology
and gotten a high-level summary of the four parts of an EJB application.
The reference chapters give more examples and more in-depth explanations of the features you have learned to use in these tutorials. Remember to take advantage of the glossary and the index as aids for getting information quickly.
Maydene Fisher has extensive experience as a technical writer specializing in the documentation of object-oriented programming languages.
Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.
|
| ||||||||||||