1. Trang chủ
  2. » Công Nghệ Thông Tin

Java Server Pages 2nd Edition phần 8 pps

62 185 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Database Access Strategies
Trường học Oracle University
Chuyên ngành Java Programming
Thể loại bài viết
Năm xuất bản 2025
Thành phố Redwood City
Định dạng
Số trang 62
Dung lượng 482,75 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 2

Chapter 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 3

Figure 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 4

regular 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 5

database 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 6

multiple 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 7

23.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 8

Figure 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 9

Figure 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 10

The 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 11

public 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 12

The 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 13

Connection 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 14

private 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 15

Servlet 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 16

ServletExec, 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 17

Connection 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 18

more 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 19

Example 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 20

oraclasses_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 21

Two 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 22

you 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 23

Example 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 24

You 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 25

SQLCommandBean 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 26

The 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 27

Part 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 28

Appendix 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 30

Note 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 31

Either 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

Ngày đăng: 13/08/2014, 21:21