Getting Started
With the exception of the new CMP approach, the EJB 2.0 features described so far provide opportunities for you to make improvements to your applications without monumental change. If you're using EJB 1.1 CMP and you want to move your entity beans to EJB 2.0, you have some work ahead of you. The benefits definitely exist, but the effort involved in reworking an existing application might not be an easy sell. Because of the amount of work involved, that's not the case we want to look at here. Even if you're using CMP, you can take steps to benefit from EJB 2.0. And because EJB 1.1 CMP is still a valid part of the specification (it hasn't been deprecated), it's not really a problem to stick with your current CMP implementation.
We also won't go into any more detail on message-driven beans here. The use of messaging in an application is a fundamental architectural decision that is beyond the scope of this article. However, if you have requirements for asynchronous messaging, you can avoid vendor-specific solutions that go outside the EJB specification by incorporating message-driven beans into your design. For example, an order-entry application might be required to send a confirmation email to the buyer when an order is placed. If the mail server is down when an order comes in, this shouldn't prevent the order from being accepted by the system, and it also shouldn't prevent the email from eventually being sent. A potential solution is to use a message-driven bean that pulls order information from a JMS destination only when the mail server is available. The bean could then build the required email messages and send them to the mail server. This approach separates the order-entry component of the system from the noncritical (in this case, anyway) task of sending email.
Access Entity Beans Using Local Clients
Because of the overhead associated with calls to an EJB, a common J2EE design approach is to expose only session beans to the clients of an application. With this approach, known as session bean wraps entity, the session beans are responsible for the workflow and for performing any required entity bean access as efficiently as possible. Under EJB 1.1, this need for efficiency has led to designs in which session beans interact with entity beans using coarse-grained methods that update or retrieve multiple fields of an entity with a single call.
As a simple example of this approach, consider a human resources application that maintains employee information using an Employee entity bean. Under EJB 1.1, the remote interface for this entity bean might include methods such as the following:
public interface Employee extends EJBObject { ... public EmployeeSnapshot getSnapshot() throws RemoteException; public void update(EmployeeSnapshot snapshot) throws RemoteException; ... }
This interface uses a simple data structure for reporting and updating the employee data. This allows session bean clients to access multiple employee fields with only a single remote call. For this example, the data structure that's used could be declared as follows:
public class EmployeeSnapshot { String employeeNumber; String firstName; String lastName; String phoneExtension; }
Although this approach is fairly straightforward to implement, it clutters the design for the sake of performance. EmployeeSnapshot is simple, but it's still another class that you have to maintain along with the code needed to copy data in and out of it whenever the employee entity is accessed. By switching to a local interface, you avoid the overhead of remote calls, but you can also do it without complicating the maintenance of your entity classes. The remote interface for the employee entity could be replaced by the following local interface:
public interface Employee extends EJBLocalObject { ... public String getEmployeeNumber(); public void setEmployeeNumber(String newEmployeeNumber); public String getFirstName(); public void setFirstName(String newFirstName); public String getLastName(); public void setLastName(String newLastName); public String getPhoneExtension(); public void setPhoneExtension(String newPhoneExtension); ... }
Notice that this interface extends EJBLocalObject instead of EJBObject and that a throws clause for RemoteException isn't needed for the methods. Because this interface supports local clients, fine-grained methods such as these can be called to access individual fields without the performance penalty incurred when taking the same approach with remote clients. You can switch to a local interface only if the clients are other EJBs running in the same container, which is typically true for an entity bean. The specification allows an EJB to support both local and remote clients, but actually needing to do that isn't common.
Switching to a local interface also requires switching to a local home interface. A local home interface still supports create and finder methods, like "remote" homes do; the only differences are in the interface hierarchy and the method parameter and return types. A local home interface extends EJBLocalHome instead of EJBHome. You also have to make sure that you reference an entity using its local interface type instead of its remote interface (if it has one) in a local home. The only effect on the bean implementation class of switching to local clients is that you can get rid of the methods written for the sake of performance to operate on multiple fields.
Extract Home Methods from Session Beans
As pointed out previously, EJB 1.1 business methods that act on more than a single object of a particular entity type are usually implemented in session beans. This is consistent with viewing session beans as controller classes that manage interactions among multiple entities, but it doesn't follow good object-oriented practices of encapsulation, loose coupling, and tight cohesion. The introduction of home methods provides an alternative to allowing entity bean business logic to spill over into session bean methods that's easy to implement.
As an example, suppose that you need a method that computes the average salary of all the employees in the human resources system. You could implement a session bean method like the one in Listing 1 to access each entity object and compute the result.
Listing 1: Accessing Multiple Entities from a Session Bean
public class HumanResourcesControllerBean implements SessionBean { ... public double computeAverageSalary() { try { // lookup Employee home interface using JNDI EmployeeHome home = ... Collection employees = home.findAll(); Iterator iter = employees.iterator(); double salaryTotal = 0.0; int numEmployees = 0; while (iter.hasNext()) { Employee employee = (Employee)iter.next(); salaryTotal += employee.getSalary(); numEmployees++; } return (numEmployees > 0) ? (salaryTotal/numEmployees) : 0.0; } catch (Exception e) { // report the exception ... } } ... }
This method is a simple one, and there's nothing incorrect about implementing it in a session bean. What it does, though, is couple the session bean to the entity bean, to some extent. Computing an average is a simple calculation, but the details are found in a class other than the one to which this method really applies. If some other steps become required for computing the average (such as ignoring part-time employees), it would be simpler to maintain this logic if it were found in the employee bean class. This way, a developer making any changes to the behavior of the employee bean related to salaries wouldn't have to worry so much about what other classes might be affected.
You can refactor this session bean method into a home method simply by adding it to the employee bean's home interface and implementing it with a name that follows the required convention (see Listing 2).
Listing 2: An Equivalent Entity Bean Home Method
public class EmployeeBean implements EntityBean { ... public double ejbHomeComputeAverageSalary() { try { // lookup Employee home interface using JNDI EmployeeHome home = ... Collection employees = home.findAll(); Iterator iter = employees.iterator(); double salaryTotal = 0.0; int numEmployees = 0; while (iter.hasNext()) { Employee employee = (Employee)iter.next(); salaryTotal += employee.getSalary(); numEmployees++; } return (numEmployees > 0) ? (salaryTotal/numEmployees) : 0.0; } catch (Exception e) { // report the exception ... } } ... }
Assuming that local interfaces are being used, the corresponding declaration in the local home interface would look like the following:
public interface EmployeeHome extends EJBLocalHome { ... public double computeAverageSalary(); ... }
Throw EJBException Instead of RemoteException
Even without converting any of your application to EJB 2.0, you can prepare your code for an eventual upgrade. Deprecation within Java is a powerful tool for phasing out functionality without breaking existing code. This term likely makes you think of the compiler warnings that you get in response to javadoc tags that have been applied to classes or methods. It's also important to pay attention to the deprecation of a previously accepted practice that doesn't always correspond to a compile-time check. For EJB developers, this applies in particular to the use of RemoteException.
Under EJB 1.0, throwing a RemoteException from an EJB method was an acceptable way to report a nonapplication error. This practice was deprecated in EJB 1.1 in favor of using EJBException. This deprecation takes on more significance in EJB 2.0 because a bean implementation class doesn't know whether it's being called by a remote client or a local one. It's up to the container, not the bean class, to determine whether a RemoteException needs to be thrown to a remote client to report a system problem. Although you'll rarely find application code that explicitly throws a RemoteException, it can be tempting to include this exception in the throws clause of an EJB method that makes remote calls on other beans. For example, Listing 3 shows an EJB 1.1 session bean method that calls an employee entity:
Listing 3: A Bean Method Declared to Throw RemoteException
public void updateEmployeeName(String employeeNumber, String newFirstName, String newLastName) throws InvalidEmployeeNumberException, RemoteException { EmployeeSnapshot snapshot = new EmployeeSnapshot(); snapshot.employeeNumber = employeeNumber; snapshot.firstName = newFirstName; snapshot.lastName = newLastName; // lookup Employee home interface using JNDI EmployeeHome home = ... try { Employee employee = home.findByEmployeeNumber(employeeNumber); employee.update(snapshot); } catch (FinderException e) { throw new InvalidEmployeeNumberException(employeeNumber, e); } }
You're required to include RemoteException in each method declaration in a remote interface, but including it in the corresponding implementation should be avoided. Rather than passing the buck and letting this exception be thrown, you should always catch RemoteException when making a remote call from an EJB and report an EJBException instead (see Listing 4).
Listing 4: An Improved Implementation That Traps RemoteException
public void updateEmployeeName(String employeeNumber, String newFirstName, String newLastName) throws InvalidEmployeeNumberException { EmployeeSnapshot snapshot = new EmployeeSnapshot(); snapshot.employeeNumber = employeeNumber; snapshot.firstName = newFirstName; snapshot.lastName = newLastName; // lookup Employee home interface using JNDI EmployeeHome home = ... try { Employee employee = home.findByEmployeeNumber(employeeNumber); employee.update(snapshot); } catch (FinderException e) { throw new InvalidEmployeeNumberException(employeeNumber, e); } catch (RemoteException e) { throw new EJBException(e); } }
This is a simple change that requires very little effort. It also gets you into the habit of following EJB 2.0 rules while keeping you conscious of the fact that clients of your beans are no longer limited to being remote clients.