When the JDBC application needs a connection to a database, it asks the DriverManager for one, and the DriverManager asks each registered Driver if it knows how to create connections for
Trang 2Chapter 23 Database Access Strategies
In this final chapter, we take a closer look at the strategies for using a database in a web application that I've mentioned in the previous chapters
In case you're new to Java database access, we start with a brief overview of the most important JDBC classes and interfaces Next, we focus in on the JDBC Connection class and how pooling Connection objects helps to solve a number of common problems We look at two ways to implement connection-pooling capabilities: the JDBC 2.0 way and by letting a JDBC 1.0 connection pool simulate a JDBC 2.0 pool
A connection pool can be made available to the rest of the application servlets as well as the JSTL database access actions in a number of ways In this chapter we discuss the approach used in Chapter 18 (using an application event listener) in more detail, as well as an approach that's more flexible but that only works in web containers that support the Java Naming and Directory Interface (JNDI)
No matter if you use a servlet or a custom action to access the database, there are a number of JDBC details that must be handled To help with this grunt work, we look at a generic database access bean that simplifies life and makes the result of a query easy to use The last section contains an example of an application-specific custom action using this bean
If you need to learn more about JDBC programming than what's covered here, I recommend that you look at the JDBC documentation online at http://java.sun.com/products/jdbc/ or read
a book about JDBC, such as George Reese's Database Programming with JDBC and Java
(O'Reilly)
23.1 JDBC Basics
The JDBC API is a set of classes and interfaces that allows a Java application to send SQL statements to a database in a vendor-independent way The API consists mostly of interfaces that define the methods you use in your program Database engine vendors and third parties provide implementations of these interfaces for a specific database engine; such an
implementation is called a JDBC driver This allows you to develop your program in a
database-independent way and connect to a specific database engine by plugging in the appropriate JDBC driver at deployment time There are JDBC drivers for most database engines on the market, both commercial and open source If you can't get one from your vendor, check out Sun's list of third-party drivers at http://industry.java.sun.com/products/jdbc/drivers
Figure 23-1 shows how the main interfaces and classes are related
Trang 3Figure 23-1 Main JDBC interfaces and classes
All JDBC core classes and interfaces belong to the java.sql package Of the types shown
in Figure 23-1, only the DriverManager is a class (part of the standard J2SE package); the rest are interfaces implemented by each unique JDBC driver
The Driver implementation is the entry point to all the other interface implementations When the Driver is loaded, it register itself with the DriverManager When the JDBC application needs a connection to a database, it asks the DriverManager for one, and the DriverManager asks each registered Driver if it knows how to create connections for the requested database When a Driver replies "yes", the DriverManager asks it for a Connection on the application's behalf; the Driver attempts to create one and return it to the application
The Connection is another core JDBC type Through the Connection instance, the JDBC application can create Statement instances of different types The main Statement type can execute a plain SQL statement, such as SELECT, UPDATE, or DELETE When a SELECT statement is executed, the result is returned as an instance of ResultSet The ResultSet has methods for navigating the result rows and asking for the column values in the current row
There are two specialized Statement types: PreparedStatement and CallableStatement For a PreparedStatement, you can specify an SQL statement where, instead of literal column values, the statement contains parameter placeholders, symbolized by question marks:
SELECT * FROM Enployee WHERE UserName = ?
Special setter methods assign values to the placeholders before the SQL statement is executed The same PreparedStatement can then be assigned new placeholder values and executed again This allows a database to parse the statement once, typically caching a strategy for how to execute it in the most efficient way, and then execute it over and over again with new values This can result in dramatically improved performance over using a
Trang 4regular Statement The PreparedStatement is also useful in other ways, as we will discuss later
PreparedStatement, you can assign values to input arguments, but in addition, there are methods for declaring the types of output arguments
Other interfaces in the JDBC API provide access to metadata about the database and the JDBC driver itself (DatabaseMetaData, available from the Connection, containing information about supported features) as well as about a ResultSet(ResultSetMetaData, available from the ResultSet, containing information about column data types, null values, etc.)
To see how it all fits together, here's a simple program that uses most of these classes:
import java.sql.*;
public class DBTest {
public static void main(String[] args) throws Exception {
// Load the JDBC Driver
Class.forName("oracle.jdbc.OracleDriver");
// Get a Connection
String url = "jdbc:oracle:thin:@myhost:1521:ORASID";
Connection conn = DriverManager.getConnection(url, "scott",
Trang 5database instance system identifier Consult the documentation for your JDBC driver to see how the URL should look like if you use a different driver
The program then creates a PreparedStatement for an SQL statement with a placeholder symbol, assigns a value to the placeholder, executes the query, and loops through all result rows represented by the ResultSet
To run a program that uses JDBC, you need to include the JDBC driver classes for your database in the classpath They are typically delivered as a JAR file, so for a web application
you just place the JAR file in the WEB-INF/lib directory If they are delivered as a ZIP file (as some of Oracle's JDBC drivers are, for instance), you can still place it in the WEB-INF/lib directory if you change the file extension from zip to.jar
23.2 Using Connections and Connection Pools
In a JDBC-based application, a lot revolves around the java.sql.Connection interface Before any database operations can take place, the application must create a Connection to the database It then acts as the communication channel between the application and the database, carrying the SQL statements sent by the application and the results returned by the database A Connection is associated with a database user account to allow the database to enforce access control rules for the SQL statements submitted through the Connection Finally, the Connection is also the boundary for database transactions Only SQL statements executed through the same Connection can make up a transaction A transaction consists of a number of SQL statements that must either all succeed or all fail as one atomic operation A transaction can be committed (the changes resulting from the statements are permanently saved) or rolled back (all changes are ignored) by calling Connectionmethods
In a standalone application, a Connection is typically created once and kept open until the application is shut down This isn't surprising, since a standalone application serves only one user at a time, and all database operations initiated by a single user are typically related to each other In a server application that deals with unrelated requests from many different users, it's not so obvious how to deal with connections There are three things to consider: a Connection is time-consuming to create, it must be used for only one user at a time to avoid transaction clashes, and it's expensive to keep open
Creating a Connection is an operation that can actually take a second or two to perform Besides establishing a network connection to the database, the database engine must authenticate the user and create a context with various data structures to keep track of transactions, cached statements, results, and so forth Creating a new Connection for each request received by the server, while simple to implement, is far too time-consuming in a high-traffic server application
One way to minimize the number of times a connection needs to be created is to keep one Connection per servlet or JSP page that need access to the database A Connection can
be created when the web resource is initialized and be kept in an instance variable until the application is shut down As you will discover when you deploy an application based on this approach, this route will lead to numerous multithreading issues Each request executes as a separate thread through the same servlet or JSP page Some JDBC drivers don't support
Trang 6multiple threads accessing the same Connection at all, causing all kinds of runtime errors Others support it by serializing all calls, leading to poor scalability Another serious problem with this approach is that requests from multiple users, all using the same Connection, operate within the same transaction If one request leads to a rollback, all other database operations using the same Connection are also rolled back
A connection is expensive to keep open in terms of server resources such as memory Many commercial database products use licenses that are priced based on the number of simultaneously open connections, so a connection can also be expensive in terms of real money Therefore, it's wise to try to minimize the number of connections the application needs An alternative to the "one Connection per resource" approach is to create a Connection for each user when the first request is received and keep it as a session scope object However, a drawback with this approach is that the Connection will be inactive most of the time, because the user needs time to look at the result of one request before making the next
The best alternative is to use a connection pool A connection pool contains a number of
Connection objects shared by all servlets and JSP pages For each request, one Connection is checked out from the pool, used, and checked back in Using a pool solves the problems described for the other alternatives:
It's time consuming to create a Connection
A pooled Connection is created only once and then reused Most pool implementations let you specify an initial number of Connection objects to create at start up, as well as a max number New Connection objects are created as needed
up to the max number Once the max number has been reached, the pool clients wait until an existing Connection object becomes available instead of creating a new one
There are multithreading problems with a shared Connection
With a pool, each request gets its own Connection so it's used by only one thread at
a time, eliminating any potential multithreading issues
A Connection is a limited resource
With a pool, each Connection is used efficiently It never sits idle if there are requests pending If the pool allows you to specify a max number of Connectionobjects, you can also balance a license limit for the number of simultaneous connections against acceptable response times
A connection pool doesn't solve all problems, however Because all users are using the same Connection objects, you can't rely on the database engine to limit access to protected data
on a per-user basis Instead, you have to define data-access rules in terms of roles (groups of users with the same access rights) You can then use separate pools for different roles, each pool creating Connection objects with a database account that represents the role
Trang 723.2.1 Using a JDBC 2.0 Optional Package Connection Pool
Connection pools exist in many forms You find them in books, articles, and on the Web Yet prior to JDBC 2.0, there was no standard defined for how a Java application would interact with a connection pool The JDBC 2.0 Optional Package (formally known as a Standard Extension), now part of JDBC 3.0 and included in the Java SDK 1.4, changed this by introducing a set of interfaces that connection pools should implement:
javax.sql.DataSource
A DataSource represents a database This is the interface the application always uses to get a Connection The class that implements the interface can provide connection-pooling capabilities or hand out regular, unpooled Connection objects; the application code is identical for both cases, as described later
javax.sql.ConnectionPoolDataSource
A DataSource implementation that provides pooling capabilities uses a class that implements the ConnectionPoolDataSource interface A ConnectionPoolDataSource is a factory for PooledConnection objects The application code never calls methods in this interface directly
javax.sql.PooledConnection
The objects a DataSource with pooling capabilities keeps in its pool implement the PooledConnection interface When the application asks the DataSource for a Connection, it locates an available PooledConnection object or gets a new one from its ConnectionPoolDataSource if the pool is empty
The PooledConnection provides a getConnection( ) method that returns a Connection object The DataSource calls this method and returns the Connection to the application This Connection object behaves like a regular Connection with one exception: when the application calls the close( ) method, instead of closing the connection to the database, it informs the PooledConnection it belongs to that it's no longer used The PooledConnection relays this information to the DataSource, which returns the PooledConnection to the pool
Figure 23-2 outlines how an application uses implementations of these interfaces to obtain
a pooled connection and how to return it to the pool
Trang 8Figure 23-2 Application using a JDBC 2.0 connection pool
The application calls the DataSource getConnection( ) method The DataSourcelooks for an available PooledConnection object in its pool If it doesn't find one, it uses its ConnectionPoolDataSource object to create a new one It then calls the getConnection( ) method on the PooledConnection object and returns the Connection object associated with the PooledConnection The application uses the Connection and calls its close( ) method when it's done This results in a notification event being sent to the DataSource, which puts the corresponding PooledConnectionobject back in the pool If you would like to learn more about the JDBC 2.0 connection pool model, you can download the JDBC 2.0 Optional Package specification or the JDBC 3.0 specification from http://java.sun.com/products/jdbc/
By implementing these JDBC 2.0 interfaces, JDBC driver vendors and middleware vendors can offer portable connection pooling implementations The latest version of the JDBC
specification, JDBC 3.0, adds statement pooling to the list of features a DataSource can provide What this means is that in addition to pooling connections, an implementation can pool prepared statements associated with each pooled connection The result can be dramatically improved performance, while leaving the application untouched; it doesn't need
to do anything different compared to using a JDBC 2.0 DataSource When I write this, very few (if any) vendors offer statement pooling, but you should ask your vendor if they support
in the Jakarta Commons project: http://jakarta.apache.org/commons/index.html In this section I describe a couple of wrapper classes you can use with minimal changes for implementations like these so they can be used in place of a JDBC 2.0 connection pool implementation This way the JSTL database access actions and other generic database tools can use your wrapped JDBC 1.0 pool, and it's easy to replace it with a real JDBC 2.0 pool when one becomes available from your database vendor or a third party
The interaction between the wrapper classes and a connection pool implementation is illustrated in Figure 23-3
Trang 9Figure 23-3 A JDBC 1.0 connection pool wrapped with JDBC 2.0 interface classes
The application calls the DataSourceWrapper getConnection( ) method The DataSourceWrapper obtains a Connection object from its ConnectionPool object (which represents the JDBC 1.0 pool implementation) The ConnectionPool either finds
an available Connection in its pool or creates a new one The DataSourceWrappercreates a new ConnectionWrapper object for the Connection it obtained or created and returns the ConnectionWrapper to the application The application uses the ConnectionWrapper object as a regular Connection The ConnectionWrapperrelays all calls to the corresponding method in the Connection it wraps except for the close( ) method When the application calls the close( ) method, the ConnectionWrapper returns its Connection to the DataSourceWrapper, which in turn returns it to its ConnectionPool
The wrapper classes included with the book examples wrap the connection pool described in
Java Servlet Programming by Jason Hunter and William Crawford (O'Reilly) It's a simple
connection pool implementation, intended only to illustrate the principles of connection pooling The source code for the connection pool is included with the code for this book, but I will not discuss the implementation of the pool itself, only how to make it look like a JDBC 2.0 connection pool using the wrapper classes For production use, I recommend that you use
a pool intended for real use instead of this code, such as one of the implementations mentioned earlier The first wrapper class is called com.ora.jsp.sql.ConnectionWrapper, shown in Example 23-1
Example 23-1 The ConnectionWrapper class
package com.ora.jsp.sql;
import java.sql.*;
import java.util.*;
class ConnectionWrapper implements Connection {
private Connection realConn;
private DataSourceWrapper dsw;
private boolean isClosed = false;
public ConnectionWrapper(Connection realConn,
DataSourceWrapper dsw) {
this.realConn = realConn;
this.dsw = dsw;
}
Trang 10The ConnectionWrapper class implements the Connection interface The implementations of all the methods except two simply relay the call to the real Connectionobject, so it can perform the requested database operation The implementation of the close( ) method, however, doesn't call the real Connect object's method Instead, it calls the DataSourceWrapper object's returnConnection( ) method, to return the Connection to the pool The isClosed( ) method, finally, returns the state of the ConnectionWrapper object as opposed to the real Connection object
Example 23-2 shows how the com.ora.jsp.sql.DataSourceWrapper gets a connection from a pool and returns it when the pool client is done with it
Example 23-2 The DataSourceWrapper class
package com.ora.jsp.sql;
import java.io.*;
import java.sql.*;
import javax.sql.*;
public class DataSourceWrapper implements DataSource {
private ConnectionPool pool;
private String driverClassName;
private String url;
private String user;
private String password;
private int initialConnections;
Trang 11public void setDriverClassName(String driverClassName) {
* Returns a Connection to the pool This method is called by
* the ConnectionWrapper's close( ) method
* Always throws an SQLException Username and password are set
* with the setter methods and can not be changed
public int getLoginTimeout( ) throws SQLException {
throw new SQLException("Not supported");
Trang 12The DataSourceWrapper class implements the DataSource interface, so it can be used
as a JDBC 2.0 connection pool implementation:
Connection conn = ds.getConnection( );
The getConnection( ) method creates an instance of the real connection pool class the first time it's called, using the JDBC driver, URL, user and password information provided through the corresponding setter methods The two most interesting methods are getConnection( ) and returnConnection( )
The pool client application calls the getConnection( ) method, and the DataSourceWrapper relays the call to the connection pool class It then wraps the Connection object it receives in a ConnectionWrapper object and returns it to the client application
As described earlier, the ConnectionWrapper object calls the returnConnection( )method when the pool client calls close( ) on the ConnectionWrapper object The returnConnection( ) method hands over the Connection to the real connection pool
so it can be recycled
All other DataSource interface methods throw an SQLException in this implementation
If you modify the wrapper classes presented here to wrap a more sophisticated connection pool, you may be able to relay some of these method calls to the real connection pool instead
The real beauty of the JDBC 2.0 connection pool interfaces is that the application doesn't have
to be aware it's using a connection pool All configuration data, such as which driver class and JDBC URL to use, the number of initial and maximum number of pooled connections, and the database account name and password, are set by a server administrator The completely configured DataSource object is made available to the application, as described in the next section, and then any component can get, use, and return a Connection with code like this:
Trang 13Connection conn = null;
23.3 Making a Connection Pool Available to Application
Components
The next part of the puzzle is how to make the DataSource available to the application components that need it In principle, there are two ways to do this The first one using an application scope variable works in any type of web container, while the second one using JNDI is more flexible but only works in a container that supports J2EE style resource access
23.3.1 Using an Application Scope Variable
One place for resources that all components in an application need access to is the application scope, corresponding to ServletContext attributes in the servlet world As I described in Chapter 18, the most appropriate component for initialization and release of this type of shared resources is the application lifecycle listener In a web container that doesn't implement the Servlet 2.3 API yet, a servlet that's loaded at startup can perform the same duties, but it's not a perfect fit.1
The container informs an application lifecycle listener when the application is started and stopped It can create the resource objects and make them available to other application components in its contextInitialized( ) method before any user requests are received, and release them when the application is shut down in its contextDestroyed( ) method Finally, a listener can use configuration data (defined as context parameters in the deployment descriptor) to work in different settings To recap, here's an application lifecycle listener similar to the one used in Chapter 18:
Trang 14private OracleConnectionCacheImpl ds = null;
public void contextInitialized(ServletContextEvent sce) {
ServletContext application = sce.getServletContext( );
String jdbcURL = application.getInitParameter("jdbcURL");
String user = application.getInitParameter("user");
String password = application.getInitParameter("password");
String maxLimit = application.getInitParameter("maxLimit");
public void contextDestroyed(ServletContextEvent sce) {
ServletContext application = sce.getServletContext( );
In the contextInitialized( ) method, the JDBC URL, database user, and password,
and the maximal number of connections to keep in the pool are read from the deployment
descriptor and used to create and configure an instance of Oracle's DataSource
implementation that provides pooling capabilities: oracle.jdbc.pool.OracleConnectionCacheImpl I'm using only some of its
features here, so you should also read Oracle's documentation if you plan to use it in your
application When the data source has been configured, it's saved as a servlet context attribute
named appDataSource To refresh your memory on the implementation and configuration
details, you may want to take a look at Chapter 18 again
An application component, such as a servlet, can pick up the DataSource registered by the
listener like this:
ServletContext application = getServletContext( );
DataSource ds = (DataSource) application.getAttribute("appDataSource");
Trang 15Servlet context attributes appear to JSP as application scope variables, so you can also tell the JSTL database actions to use this DataSource by specifying it with the dataSourceattribute:
<sql:query dataSource="${appDataSource}" />
If you want to make the DataSource the default used by the JSTL database actions, you must use the application scope variable name they expect, controlled by the javax.servlet.jsp.jstl.core.Config class described in Chapter 22:
public void contextInitialized(ServletContextEvent sce) {
ServletContext application = sce.getServletContext( );
String jdbcURL = application.getInitParameter("jdbcURL");
String user = application.getInitParameter("user");
String password = application.getInitParameter("password");
String maxLimit = application.getInitParameter("maxLimit");
import javax.servlet.jsp.jstl.core.Config;
ServletContext application = getServletContext( );
DataSource ds =
(DataSource) Config.get(application, Config.SQL_DATASOURCE);
The listener also implements the contextDestroyed( ) method, called by the container before the application is shut down In this method, the context attribute is removed, and all connections in the data source are closed How to gracefully shut down a DataSource isn't defined by the JDBC specification, but for Oracle, you do it by calling the close( ) method
on the OracleConnectionCacheImpl instance
Trang 16ServletExec, provide resource access through JNDI even though the servlet and JSP specifications don't require it
To use JNDI, you first define the resource in the web application deployment descriptor, using the <resource-ref> element (between the <taglib> and <security-constraint> elements, if any):
The <res-ref-name> element is mandatory and must contain a unique name that is used
by the application components to retrieve the resource, as you will see shortly For a source resource, the J2EE specification recommends that you use the naming convention shown here, i.e., a name in the JNDI jdbc subcontext
data-The type of the resource must be defined by the <res-type> element It must be the fully qualified class name for the resource, and for a data source, it's always javax.sql.DataSource
Next comes the <res-auth> element It accepts one of two values: Container or Application Container means that database account information needed to get connections from the data source must be provided to the container when the data source is registered as a JNDI resource, so the container can take care of authentication Application means that the application will provide this information every time it gets a connection This boils down to whether the application will call getConnection( ) (in the container-controlled authentication case) or getConnection(String username, String password) (in the application-controlled case) In most cases you want the container to take care of it
The <res-sharing-scope> element is optional and accepts one of Sharable or Unsharable This element tells the data source if it should return the same connection when being asked for one multiple times within the same transaction (if the transaction is controlled
by the container or the Java Transaction API, JTA) or if it should return a unique connection each time If you use only the JDBC transaction control methods, commit( ) and rollback( ), this element doesn't matter because the connections can never be shared The default is Sharable, and that's fine for almost all cases
Application components servlets, custom actions, beans, or any other type of class used by the application use the JNDI API to grab the DataSource and a Connection like this:
Trang 17Connection conn = ds.getConnection( );
The InitialContext represents the entry point to the container's JNDI resource naming service The lookup( ) method argument is the path for the DataSource The first part, java:comp/env/ is the base for all J2EE resources, followed by the value declared by the
<res-ref-name> element in the deployment descriptor With the DataSource retrieved through JNDI, the application gets a Connection by calling getConnection( ) as usual
When you use the JSTL database actions, you can specify a JNDI path as the data source, either as the corresponding configuration setting as described in Chapter 22 or as the dataSource attribute value:
<sql:query dataSource="jdbc/Example" />
The path must be the path relative to the J2EE base; in other words, the same value as you define with the <res-ref-name> element
All I've said about how to declare the resource in the deployment descriptor and get access to
it through JNDI is defined by the J2EE and servlet specifications How to register a DataSource with a container's naming service, however, is a process that differs between containers I'll show you how it's done for Tomcat 4, but you need to read the documentation
to see how to do it for other containers
For Tomcat 4, resource registration is done in the conf/server.xml file This is the main
configuration file for Tomcat To register the JNDI resource, you must use a <Context>
element to declare your web application explicitly in the conf/server.xml file (just placing it in Tomcat's webapps directory isn't enough in this case) and add a nested <ResourceParams>
element to register and configure the JNDI DataSource factory for your application:
<Server port="8005" shutdown="SHUTDOWN" debug="0">
<! Book examples context >
<Context path="/ora" docBase="ora">
Trang 18more about the conf/server.xml file and all its elements, I suggest you read the Tomcat 4 Server Configuration Reference, available at http://jakarta.apache.org/tomcat/tomcat-4.0-
doc/config/index.html
The <ResourceParams> element's name attribute must be set to the same name as you defined for the resource with the <res-ref-name> element in the deployment descriptor With exception for factory and dataSourceClassName, the nested <parameter>elements set the parameter values supported by the specific DataSource you use In this example I use some of the parameters supported by the Oracle OracleConnectionCacheImpl data source
The factory parameter identifies the JNDI object factory Tomcat uses to create the data source object A JNDI object factory is a class that implements the single method defined by the javax.naming.spi.ObjectFactory interface Some JDBC drivers may bundle an object factory that produces data-source objects for the implementations included with the driver, but here I use a generic data source factory that I implemented for this book It uses introspection to set the parameters for any DataSource implementation, for instance the Oracle connection pool data source in this example
The object factory source code is shown in Example 23-3
Trang 19Example 23-3 A generic DataSource factory class
public class DataSourceFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name,
Context nameCtx, Hashtable environment)
throws NamingException {
System.out.println("Generic factory called");
Reference ref = (Reference) obj;
RefAddr addr = (RefAddr) addrs.nextElement( );
String prop = addr.getType( );
String value = (String) addr.getContent( );
of the DataSource class specified by the dataSourceClassName parameter in the
config/server.xml file, calls all setter methods matching the parameters specified within the
<ResourceParams> element, and returns the configured instance A number of private methods, not shown here, use the Introspection API to find and call the setter methods for the parameters The source code is bundled with the book examples, so you can look at these details at your leisure
When you use JNDI, you must also place the JDBC driver classes in a directory that Tomcat
itself can use: namely in the common/lib directory if they are packaged in a JAR file, otherwise in common/classes Classes in the WEB-INF/lib and WEB-INF/classes directories
are available only to the application, not the container, so they are no good in this case The same goes for the factory class The factory class shown in Example 23-3 is part of the
Trang 20oraclasses_2_0.jar file, located in the WEB-INF/lib directory for the book examples application To use this factory, you must move the JAR file to the common/lib directory
23.4 Using a Generic Database Bean
Some consider using the JSTL database access actions in JSP pages as putting too much business logic in the presentation layer (the View), and therefore think it's something that should be avoided For a very simple application, it's no big deal, but for a more complex application it's better to move the SQL statements to some other component type You have basically two options: move it to a Controller servlet (or an action class that the servlet delegates to), as in the Chapter 18 example, or encapsulate it in a custom action In both cases
it makes sense to add yet another abstraction layer in the form of a bean that encapsulates the SQL statements and let the servlet or tag handler access the data in a purer form One example
of such a bean is the EmployeeRegistryBean used in Chapter 18 for authentication as well as for retrieving and saving information about an employee
When you develop this type of database access components, you can of course use the JDBC API directly I find it handy to use a generic JDBC bean, such as the com.ora.jsp.beans.sql.SQLCommandBean described in this section Besides taking care of a lot of the grunt work, it also converts a query result into an instance of the same class that the JSTL <sql:query> action uses to expose the result This makes it easy to use a JSP page that renders the result
The SQLCommandBean has three write-only properties Example 23-4 shows the beginning
of the class file with the setter methods
Example 23-4 SQLCommandBean property setter methods
package com.ora.jsp.beans.sql;
import java.util.*;
import java.sql.*;
import javax.servlet.jsp.jstl.sql.*;
public class SQLCommandBean {
private Connection conn;
private String sqlValue;
private List values;
public void setConnection(Connection conn) {
Trang 21Two methods in the SQLCommandBean execute the SQL statement: the executeQuery( ) method for a SELECT statement and the executeUpdate( )method for all other types of statements Example 23-5 shows the executeQuery( )method
Example 23-5 The SQLCommandBean's executeQuery( ) method
public Result executeQuery( ) throws SQLException {
Result result = null;
ResultSet rs = null;
PreparedStatement pstmt = null;
Statement stmt = null;
try {
if (values != null && values.size( ) > 0) {
// Use a PreparedStatement and set all values
You may wonder why a Result object is created and returned instead of returning the ResultSet directly The reason is that a ResultSet is tied to the Connection that was used to generate it When the Connection is closed or executes a new SQL statement, all open ResultSet objects for the Connection are released You must therefore make sure
Trang 22you save the information from the ResultSet in a new data structure before reusing the Connection or return it to the pool
The code for the creation of the PreparedStatement or Statement object and the execution of the statement is enclosed in a try/finally block This is important, because
if something fails (due to an invalid SQL statement, for instance), the JDBC methods throw
an SQLException The exception should be handled by the application using the SQLCommandBean, but first, all JDBC resources must be released, and the Connectionobject returned to the pool Using a try block with a finally clause but no catch clause provides this behavior If an exception is thrown, the finally clause is executed, and the exception is automatically thrown to the object that called the executeQuery( ) method
In the finally clause, the ResultSet object and either the PreparedStatement or Statement object are closed It should be enough to close the statement object according to the JDBC specification (closing the statement should also close the ResultSet associated with the statement) but doing it explicitly doesn't hurt and makes the code work even with a buggy JDBC driver Each resource is closed within its own try/catch block, since the close( ) method can also throw an exception
Example 23-6 shows the setValues( ) method
Example 23-6 The SQLCommandBean's setValues( ) method
private void setValues(PreparedStatement pstmt, List values)
throws SQLException {
for (int i = 0; i < values.size( ); i++) {
Object v = values.get(i);
// Set the value using the method corresponding to the type
// Note! Set methods are indexed from 1, so we add 1 to i
is used here, since it's used only once It's true that a PreparedStatement is intended to be reused over and over again to execute the same SQL statement with new values However, it offers a convenient solution to the problem with different literal value syntax for date/time and number column values When a PreparedStatement is used, the placeholders in the SQL statement can be set using the appropriate Java types instead, without worrying about what literal representation a certain JDBC driver supports So even though it's only used once here, a PreparedStatement still has an advantage over a regular Statement
The executeUpdate( ) method, shown in Example 23-7, is very similar to the executeQuery( ) method
Trang 23Example 23-7 The SQLCommandBean's executeUpdate( ) method
public int executeUpdate( ) throws SQLException {
if (values != null && values.size( ) > 0) {
// Use a PreparedStatement and set all values
23.5 Developing Application-Specific Database Components
The SQLCommandBean class described in this chapter can be used for application specific components that access a database The bean is used like this:
SQLCommandBean sqlCommandBean = new SQLCommandBean( );
Result result = sqlCommandBean.executeQuery( );
Chapter 18 includes a more advanced example of an application-specific bean (the EmployeeRegisterBean) that uses the SQLCommandBean
Trang 24You can also use these classes in your application-specific custom actions One example is the custom action that's mentioned in Chapter 11 as an alternative to the generic database actions for inserting or updating employee information:
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jstl/sql" %>
<%@ taglib prefix="myLib" uri="/mytaglib" %>
<myLib:saveEmployeeInfo dataSource="example" />
<% Get the new or updated data from the database %>
<sql:query var="newEmpDbInfo" dataSource="${example}" scope="session">
SELECT * FROM Employee
Example 23-8 shows one way to implement this custom action
Example 23-8 SaveEmployeeInfoTag class
public class SaveEmployeeInfoTag extends TagSupport {
private String dataSourceName;
public void setDataSource(String dataSourceName) {
this.dataSourceName = dataSourceName;
}
public int doEndTag( ) throws JspException {
// Get all request parameters
ServletRequest request = pageContext.getRequest( );
String userName = request.getParameter("userName");
String password = request.getParameter("password");
String firstName = request.getParameter("firstName");
String lastName = request.getParameter("lastName");
String dept = request.getParameter("dept");
String empDateString = request.getParameter("empDate");
String emailAddr = request.getParameter("emailAddr");
if (userName == null || password == null ||
firstName == null || lastName == null ||
dept == null || empDateString == null ||
emailAddr == null) {
throw new JspException("Missing a mandatory parameter");
}
Trang 25SQLCommandBean sqlCommandBean = new SQLCommandBean( );
"SELECT * FROM Employee WHERE UserName = ?";
List values = new List( );
values.add(userName);
sqlCommandBean.setSqlValue(sqlValue);
sqlCommandBean.setValues(values);
Result result = sqlCommandBean.executeQuery( );
// Create values for insert/update
new java.sql.Date(empDate.getTime( ));
values.add(empSQLDate);
values.add(emailAddr);
values.add(new Timestamp(System.currentTimeMillis( ))); values.add(userName);
if (result.getRowCount( ) == 0) {
// New user Insert
StringBuffer sb = new StringBuffer( );
sb.append("INSERT INTO Employee ")
append("(Password, FirstName, LastName, Dept, ") append("EmpDate, EmailAddr, ModDate, UserName) ") append("VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
sqlCommandBean.setSqlValue(sb.toString( ));
}
else {
// Existing user Update
StringBuffer sb = new StringBuffer( );
sb.append("UPDATE Employee ")
append("SET Password = ?, FirstName = ?, ") append("LastName = ?, Dept = ?, EmpDate = ?, ") append("EmailAddr = ?, ModDate = ? ")
Trang 26The tag handler uses the bean to execute a SELECT statement to find out if the specified employee is already defined in the database If not, the tag handler sets the bean's SQL statement to an INSERT statement and executes it with all the information provided through the request parameters; otherwise the tag handler uses the bean to execute an UPDATEstatement
The tag handler class described here is intended to show you how to use the database access classes to implement your own custom actions The tag handler class can be improved in several ways For instance, it can support an EL value for the dataSource attribute and provide default values for missing parameters, such as the current date for a missing employment date and an email address based on the employee's first and last name if the email address is missing You can also use a bean as input to the action instead of reading request parameters directly This allows the bean to be used as described in Chapter 8 to capture and validate user input until all information is valid, and then pass it on to the custom action for permanent storage of the information in a database Finally, it's a good idea to encapsulate all database access in a bean, such as the EmloyeeRegistryBean, and use this bean in the tag handler class instead of using the SQLCommandBean directly
Trang 27Part IV: Appendixes
In this part of the book, you'll find reference material, such as descriptions of JSP and JSTL elements and classes, the JSTL Expression Language, all book example components, and the web application deployment descriptor
Trang 28Appendix A JSP Elements Reference
JSP defines three types of elements: directives, scripting elements, and action elements In addition, you can define your own custom actions This appendix contains descriptions of all JSP elements as well as the general syntax rules for custom actions
Each element is described with an overview, a syntax reference, an attribute table, and an example The syntax reference shows all supported attributes, with optional attributes embedded in square brackets ([]) Mutually exclusive attributes are separated with vertical bars (|) For attributes that accept predefined values, all values are listed separated with vertical bars; the default value (if any) is underlined Italics are used for attribute values that don't have a fixed set of accepted values
A.1 Directive Elements
Directive elements are used to specify information about the page itself, especially information that doesn't differ between requests for the page The general directive syntax is:
<%@ directiveName attr1="value1" attr2="value2" %>
For a JSP Document (a JSP page written in XML syntax), the general syntax is:
<jsp:directive.directiveName attr1="value1" attr2="value2" />
Only the syntax for regular JSP pages is shown in the detailed sections that follow
The attribute values can be enclosed with single quotes instead of double quotes The directive name and all attribute names are case-sensitive
file No default A page- or context-relative URI path for the file to include
A page can contain multiple include directives The including page and all included pages
taken together form what is called a JSP translation unit
Trang 29<@ page [autoFlush="true|false"] [buffer="8kb|NNkb|none"]
[contentType="mimeType"] [errorPage="pageOrContextRelativePath"]
[extends="className"] [import="packageList"] [info="info"]
buffer 8kb
Specifies the buffer size for the page The value must be expressed as the size in kilobytes followed by kb, or be the keyword none to disable buffering
contentType text/html
or text/xml
The MIME type for the response generated by the page and optionally the response charset, e.g., text/html;charset=Shift_JIS The charset applies to the JSP page file as well, if pageEncoding isn't specified
The default MIME type is text/html for a regular JSP page and text/xml for a JSP Document
If no charset is specified, ISO-8859-1 is used for a regular JSP page and UTF-8 for a JSP Document
errorPage No default A page- or context-relative URI path for the JSP page, servlet, or static page to forward to in case an exception is thrown by code
in the page
Trang 30Note that the recommendation is to not use this attribute
Specifying your own superclass restricts the JSP container's ability to provide a specialized, high-performance superclass
import No default
A Java import declaration, i.e., a comma-separated list of fully qualified class names or package names followed by * (for all public classes in the package)
info No default Text that a web container may use as a description of the page in its administration user interface
isErrorPage false
Set to true for a page that is used as an error page, to make the implicit exception variable available to scripting elements Use false for regular JSP pages
language java Defines the scripting language used in the page
a regular JSP page, and UTF-8 is used for a JSP Document
session true
Set to true if the page should participate in a user session If set
to false, neither the session scope nor the implicit sessionvariable is available to JSP elements in the page
A translation unit (the JSP source file and any files included via the include directive) can contain more than one page directive, as long as there is only one occurrence of an attribute, with the exception of the import attribute If multiple import attribute values are used, they are combined into one list of import definitions
Example
<%@ page language="java" contentType="text/html;charset=Shift_JIS"%>
<%@ page import="java.util.*, java.text.*" %>
<%@ page import="java.sql.Date" %>
Trang 31Either a symbolic name for the tag library that is defined in the TLD for
the library or in the web.xml file for the application, or a page-relative
or context-relative URI path for the library's TLD file or JAR file
Examples
<%@ taglib prefix="ora" uri="orataglib" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
A.2 Scripting Elements
The scripting elements let you add small pieces of code in a JSP page, such as an ifstatement to generate different HTML depending on some condition The scripting code must
be written in the language defined by the page directive It's executed when the JSP page is requested
Declaration
Declarations are used to declare a scripting language variable or method The content must be
a complete valid declaration in the scripting language defined by the page directive The JSP implicit variables aren't visible in a declaration element
When the scripting language is Java, a variable declared by a declaration element ends up as
an instance variable in the JSP page implementation class It's therefore visible to parallel threads (requests) processing the page and needs to be handled in a thread-safe manner A thread-safe alternative is to declare variables within a scriptlet element instead It then becomes a local variable of the method in the page implementation class used to process each request and isn't shared by parallel threads