Chapter 10. External Services
Topics in This Chapter |
"Database Access with JDBC" on page 451
"Configuring a Data Source" on page 457
"An Introduction to LDAP" on page 473
"Managing Configuration Information" on page 483
"Container-Managed Authentication and Authorization" on page 505
"Using Web Services" on page 516
|
In this
chapter, you learn how to access external services from your JSF application. We
show you how to connect to databases, directory services, and web services. Our
primary interest lies in the clean separation between the application logic and
the configuration of resources.
Database Access with JDBC
In the following sections, we give you a
brief refresher of the JDBC (Java Database Connectivity) API. We assume that you
are familiar with basic SQL (Structured Query Language) commands. A more
thorough introduction to these topics can be found in Horstmann and Cornell,
2004, 2005. Core Java™ 2, vol. 2, chap. 4. For your convenience, here is a brief
refresher of the basics.
Issuing SQL Statements
To issue SQL statements to a
database, you need a connection object. There are various methods of obtaining a connection.
The most elegant one is to use a data source.
DataSource source = . . .
Connection conn = source.getConnection();
The section "Accessing
a Container-Managed Resource" on page 462 describes how to obtain a data source in the GlassFish and
Tomcat containers. For now, we assume that the data source is
properly configured to connect to your favorite database.
Once you have the Connection object, you create a
Statement object that you use to send SQL
statements to the database. You use the executeUpdate method for SQL statements that update the database and
the executeQuery method for queries that
return a result set.
Statement stat = conn.createStatement();
stat.executeUpdate("INSERT INTO Users VALUES ('troosevelt', 'jabberwock')");
ResultSet result = stat.executeQuery("SELECT * FROM Users");
The ResultSet class
has an unusual iteration protocol. First you call the next method to advance the cursor to the first row. (The
next method returns false if no further
rows are available.) Then you call the getString method to get a field
value as a string. For example,
while (result.next()) {
username = result.getString("username");
password = result.getString("password");
. . .
}
When you are done using the database,
be certain that you close the connection. To ensure that the connection is
closed under all circumstances, even when an exception occurs, wrap the query
code inside a try/finally block, like this:
Connection conn = source.getConnection();
try {
. . .
}
finally {
conn.close();
}
Of course, there is much more to the
JDBC API, but these simple concepts are sufficient to get you started.
Note
|
Here we show you how to execute SQL statements from your web application. This approach is fine for lightweight applications that have modest storage requirements. For complex applications, you would want to use an object-relational mapping technology such as JPA (the Java Persistence Architecture) or Hibernate. |
Connection Management
One of the more
vexing issues for the web developer is the management of database connections.
There are two conflicting concerns. First, opening a connection to a database
can be time consuming. Several seconds may elapse for the processes of
connecting, authenticating, and acquiring resources to be completed. Thus, you
cannot simply open a new connection for every page request.
On the flip side, you cannot keep open a
huge number of connections to the database. Connections consume resources, both
in the client program and in the database server. Commonly, a database puts a
limit on the maximum number of concurrent connections that it allows. Thus, your
application cannot simply open a connection whenever a user logs on and leave it
open until the user logs off. After all, your user might walk away and never log
off.
One common mechanism for solving
these concerns is to pool the database connections. A connection pool holds
database connections that are already opened. Application programs obtain
connections from the pool. When the connections are no longer needed, they are
returned to the pool, but they are not closed. Thus, the pool minimizes the time
lag of establishing database connections.
Implementing a database
connection pool is not easy, and it certainly should not be the responsibility
of the application programmer. As of version 2.0, JDBC supports pooling in a
pleasantly transparent way. When you receive a pooled Connection object, it is actually instrumented so that its
close method merely returns it to the
pool. It is up to the application server to set up the pool and to give you a
data source whose getConnection method yields pooled connections.
Each application server has its
own way of configuring the database connection pool. The details are not part of
any Java standard—the JDBC specification is completely silent on this issue. In
the next section, we describe how to configure GlassFish and Tomcat for
connection pooling. The basic principle is the same with other application
servers, but of course the details may differ considerably.
To maintain the pool, it is still
essential that you close every connection object when you are done using it.
Otherwise the pool will run dry, and new physical connections to the database
will need to be opened. Properly closing connections is the topic of the next
section.
Plugging Connection Leaks
Consider this simple sequence of statements:
DataSource source = ...
Connection conn = source.getConnection();
Statement stat = conn.createStatement();
String command = "INSERT INTO Users VALUES ('troosevelt', 'jabberwock')";
stat.executeUpdate(command);
conn.close();
The code looks clean—we open a
connection, issue a command, and immediately close the connection. But there is
a fatal flaw. If one of the method calls throws an exception, the call to the
close method never happens!
In that case, an irate user may
resubmit the request many times in frustration, leaking another connection
object with every click.
To overcome this issue, always place the call to close inside a finally
block:
DataSource source = ...
Connection conn = source.getConnection();
try {
Statement stat = conn.createStatement();
String command = "INSERT INTO Users VALUES ('troosevelt', 'jabberwock')";
stat.executeUpdate(command);
}
finally {
conn.close();
}
This simple rule completely
solves the problem of leaking connections.
The rule is most effective if you
do not combine this try/finally construct with any other exception handling code. In
particular, do not attempt to catch a SQLException in the same
try block:
// we recommend that you do NOT do this
Connection conn = null;
try {
conn = source.getConnection();
Statement stat = conn.createStatement();
String command = "INSERT INTO Users VALUES ('troosevelt', 'jabberwock')";
stat.executeUpdate(command);
}
catch (SQLException) {
// log error
}
finally {
conn.close(); // ERROR
}
That code has two subtle mistakes.
First, if the call to getConnection throws an exception, then
conn is still null, and you can't call close.
Moreover, the call to close can also throw a SQLException. You could clutter up the finally
clause with more code, but the result is a mess. Instead, use two separate
try blocks:
// we recommend that you use separate try blocks
try {
Connection conn = source.getConnection();
try {
Statement stat = conn.createStatement();
String command = "INSERT INTO Users VALUES ('troosevelt', 'jabberwock')";
stat.executeUpdate(command);
}
finally {
conn.close();
}
}
catch (SQLException) {
// log error
}
The inner try block ensures that the connection is closed. The
outer try block ensures that the exception
is logged.
Note
|
Of course, you can also tag your method with throws SQLException and leave the outer try block to the caller. That is often the best solution. |
Using Prepared Statements
A common optimization technique for JDBC
programs is the use of the Prepared-Statement class. You use a prepared statement to speed
up database operations if your code issues the same type of query multiple
times. Consider the lookup of user passwords. You will repeatedly need to issue
a query of the form
SELECT password FROM Users WHERE username=...
A prepared statement asks the
database to precompile a query—that is, parse the SQL statement and compute a
query strategy. That information is kept with the prepared statement and reused
whenever the query is reissued.
You create a prepared statement with the
prepareStatement method of the Connection class. Use a
? character for each parameter:
PreparedStatement stat = conn.prepareStatement(
"SELECT password FROM Users WHERE username=?");
When you are ready to issue a
prepared statement, first set the parameter values.
(Note that the index value 1 denotes the
first parameter.) Then issue the statement in the usual way:
ResultSet result = stat.executeQuery();
At first glance, it appears as if
prepared statements would not be of much benefit in a web application. After
all, you close the connection whenever you complete a user request. A prepared
statement is tied to a database connection, and all the work of establishing it
is lost when the physical connection to the database is terminated.
However, if the physical
database connections are kept in a pool, then there is a good chance that the
prepared statement is still usable when you retrieve a connection. Many
connection pool implementations will cache prepared statements.
When you call prepareStatement, the pool will first look inside the statement cache,
using the query string as a key. If the prepared statement is found, then it is
reused. Otherwise, a new prepared statement is created and added to the
cache.
All this activity is transparent to the application programmer.
You request PreparedStatement objects and
hope that, at least some of the time, the pool can retrieve an existing object
for the given query.
Caution
|
You cannot keep a PreparedStatement object and reuse it beyond a single request scope. Once you close a pooled connection, all associated PreparedStatement objects also revert to the pool. Thus, you should not hang on to PreparedStatement objects beyond the current request. Instead, keep calling the prepareStatement method with the same query string, and chances are good that you will get a cached statement object. |
Note
|
Even if you are not interested in performance, there is another good reason to use prepared statements: to guard against SQL injection attacks. |
No comments:
Post a Comment