| CONTENTS | PREV | NEXT | INDEX | Designing Enterprise Applications with the J2EETM Platform, Second Edition |
The best way to achieve application portability is to use only J2EE-compatible application servers and tools. These products pass the rigorous J2EE Compatibility Test Suite (CTS) that ensures "Write-Once-Run-Anywhere" portability for J2EE applications.
Another way to ensure portability is to use more than one application server in the development cycle. The J2EE Reference Implementation (J2EE RI) is a good choice for a second application server.
It is also helpful to use code generation wizards to write enterprise beans. Besides enhancing productivity, such wizards typically generate code that is consistent with the J2EE specifications, thereby eliminating inadvertent errors. For example, such wizards will typically ensure that your enterprise bean methods throw all required exceptions--RemoteException, FinderException, and so on. These tools are particularly useful for generating the deployment descriptor since the deployment descriptor content must follow a precise XML syntax. Writing them manually is prone to errors because it is easy to incorrectly order elements, duplicate element entries, or make simple typing mistakes.
Most application servers also ship with verification tools that validate the components and their deployment descriptors against the J2EE specifications. The J2EE RI also ships with one such tool called the J2EE Verifier. Run these verification tools as part of your regular build process to enhance portability.
A client program that needs to access a remote enterprise bean must use the PortableRemoteObject.narrow method for type narrowing. Type narrowing is needed when a client program looks up a home interface from JNDI, or a finder method returns a collection of references to remote enterprise beans. Code Example 5.9 shows how to do type narrowing when looking up a home object from JNDI.
try {
Context ctxt = new InitialContext();
Object objref = ctxt.lookup("java:comp/env/ejb/remote/admin");
OrderProcessingCenterAdminFacadeHome adminHome =
(OrderProcessingCenterAdminFacadeHome) PortableRemoteObject.narrow
(objref, OrderProcessingCenterAdminFacadeHome.class);
OrderProcessingCenterAdminFacade admin = adminHome.create();
} catch (....) {
Code Example 5.9 Using the narrow Method for Type Narrowing
|
Type narrowing is needed because many application servers use RMI-IIOP as the communication protocol to access remote beans. However, some application servers do not use RMI-IIOP and hence allow the use of Java language typecasts as well. For portability you cannot rely on an application server allowing Java language typecasts; you should always use the PortableRemoteObject.narrow method. The overhead on this method call is usually quite small. In addition, EJB containers that do not use RMI-IIOP typically optimize away all such overhead.
Note that the narrow method must not be used in the clients of local enterprise beans since they are in the same JVM. The local enterprise beans are always type-narrowed using the regular typecast of the Java programming language.
To preserve a bean's state during passivation, the bean class must be serializable. This requires that all the non-transient fields of the bean class are serializable. Fields that are primitive types, such as String and int, are serializable. However, a reference field, which is a field whose value is a reference to a class instance, is serializable only if the referenced class implements java.io.Serializable. You must mark all fields of non-serializable types as transient. The JVM's serialization machinery ignores fields marked as transient.
For example, a database connection represented by java.sql.Connection is not serializable. It must be marked transient when declared inside an enterprise bean class.
Extra effort is required to achieve portability for an enterprise bean that uses bean-managed persistence, because the bean needs to ensure portability across all databases as well as JDBC drivers.
The foremost factor affecting portability relates to the SQL language. Many database vendors provide proprietary extensions to SQL to provide additional functionality and to achieve higher performance. Consider using only standard SQL constructs to achieve portability. If you do need to use proprietary extensions, consider using the Data Access Object design pattern to encapsulate vendor-specific code.
5.8.3.1 SQL and Database ConnectionsFor maximum portability, it's important to close SQL statements before you close the database connection. Enterprise beans often need to open a database connection, execute a set of SQL statements, then close the connection. Some JDBC driver implementations throw an exception if a JDBC connection is closed while some of the driver database statements are open. To achieve portability across JDBC drivers, always close database statements before closing the database connection. The finally block in Code Example 5.10 illustrates how this can be done.
public Page searchItems(String searchQuery, ....) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
String query = "SELECT .....";
try {
con = dataSource.getConnection();
ps = con.prepareStatement(query);
rs = ps.executeQuery();
// rest of the method body
} catch (.... ) { // handle exceptions
} finally {
if (rs != null) rs.close();
// Close PreparedStatement before Connection.
if (ps != null) ps.close();
if (c != null) c.close();
}
...
}
| Code Example 5.10 Closing Database Connections |
5.8.3.2 Relying on Instance Fields
Bean providers should not rely on a bean's instance fields or container-managed persistence accessor methods within ejbActivate, ejbLoad, ejbPassivate, and ejbStore methods. This is because the container can choose several ways to manage the life cycle of its enterprise beans. For example, in the ejbActivate method the container is not required to load an entity bean's instance fields from its persistence store. Similarly, in the ejbPassivate method the container is not required to store the instance fields to its persistence store. In addition, the container is not required to allow accesses to resources from the ejbActivate or ejbPassivate methods.
Bean providers should be especially careful to avoid including backend resource-specific details in their components' interfaces, since doing so may limit where the components might be used. One easily overlooked form of resource dependence is the set of exceptions a method may throw. Because bean-managed persistence methods do not necessarily use a SQL database to manage their persistence, SQLException should not be thrown in the bean-managed persistence method signatures.
Instead of throwing SQLException, define system- and application-level exceptions for the class and throw those exceptions in response to error conditions. While using the Data Access Object (DAO) design pattern, catch the resource-specific exceptions, such as SQLException, in the DAO class and translate them to appropriate system-level or application-level exceptions.
Consider Code Example 5.11 from the sample application. In the method searchItems, an SQLException is translated to a CatalogDAOSysException, which extends java.lang.RuntimeException to indicate a system-level exception.
public class CatalogDAOImpl implements CatalogDAO {
....
public Page searchItems(String searchQuery, int start,
int count, Locale l)
throws CatalogDAOSysException {
....
try {
Connection con = getDBConnection();
PreparedStatement ps = con.prepareStatement("SELECT ...");
...
ps.executeQuery();
...
} catch (SQLException se) {
throw new CatalogDAOSysException("Malformed query.");
}
...
}
| Code Example 5.11 Throwing Exceptions |
The code throws an application exception if the user input to searchQuery is incorrect. For errors such as an unavailable database connection, or general SQL exceptions, a system exception should be thrown.