Now that we've considered which type of session bean to use and some idioms frequently used with session beans, let's take a look at some important implementation issues concerning session beans. In this section we'll consider the EJB error handling model and how it affects bean developers; transaction propagation using EJB CMT and its implications for implementing session beans; and an EJB implementation pattern that can help us to avoid a common cause of deployment errors.
As EJBs are managed objects, the EJB container steps in to handle some types of exceptions they throw. This section discusses the rules defined in the EJB specification, and how developers can use them to advantage. These considerations are important as we implement business logic in session beans and define session bean local and remote interfaces.
The EJB specification's approach to exceptions thrown by EJBs is simple and elegant. The specification distinguishes between app exceptions and all other exceptions, referred to as system exceptions. An app exception is a checked exception defined in the throws clause of a method of an EJB's local or remote home or local or remote interface, other than java.remote.RemoteException. (Remember that Java RMI requires that all remote methods declare this exception in their throws clause.) A system exception is an unchecked exception or throwable thrown by an EJB implementation class method at run time, or an uncaught java.remote.RemoteException resulting from a call on another EJB within the app. (It's up to the bean developer to decide whether or not a method in a bean class catches RemoteExceptions resulting from calling other EJBs. It will usually only make sense to do so if the error is recoverable, or to add context information to the nested exception.) It is assumed that the EJB client understands app exceptions, and how they might be recovered from. It is up to the client to decide that a particular exception is unrecoverable. Consequently, the container does not step in when an app exception is thrown. It simply causes the EJB client to catch the same app exception. The container will not normally log app exceptions, and the status of the current transaction will be unaffected. System exceptions are handled very differently. The container assumes that the client didn't expect such an exception, and that it's likely to prove fatal for the current use case. This is a reasonable assumption. In contrast to app exceptions, which are checked and which the client must know about because it is forced by the Java compiler to catch them, system exceptions may be meaningless to the client. Take, for example, a runtime exception from a JDO persistence manager. The client should not even know the EJB tier's persistence mechanism, and can't be expected to recover from a problem it doesn't understand. Accordingly, the container takes drastic action. It marks the current transaction irreversibly for rollback. It discards the EJB instance so that it can service no further calls. The fact that the EJB instance will be discarded means that the developer need not make any effort to clean up any conversational or internal state maintained in the bean. This reduces developer workload and removes a potential source of bugs. The EJB container must log the offending system exception. If the client is remote, the EJB container throws a java.remote.RemoteException (or a subclass) to the client. If the client is local, the EJB container throws a javax.ejb.EJBException to the client. A bean that has encountered an unrecoverable checked exception that it cannot rethrow should throw a javax.ejb.EJBException or subclass wrapping the checked exception. This will be treated as an unexpected exception by the container. This container behavior on unexpected exceptions should not be used as a substitute for explicitly rolling back transactions using the setRollbackOnly() method. It is a convenient cleanup facility offered by the container when a bean instance encounters an unrecoverable error. Since it results in the bean instance being discarded, it will reduce performance if this situation occurs frequently at run time. Let's now consider some of the implications when designing and implementing session beans:
We must never allow an EJB method to throw a runtime exception in normal operation
An uncaught throwable will cause not only the call stack to unwind as usual, but also the container to roll back the current transaction.
Important |
The guarantee that the EJB container will step in to handle an unchecked exception and ensure transaction rollback makes it particularly attractive to define fatal exceptions thrown by helper classes likely to be used by EJBs to be unchecked exceptions. For example, the JDBC abstraction framework discussed in throws the unchecked DataAccessException, which EJBs that use it can simply ignore, on encountering an unrecoverable java.sql.SQLException. In the unlikely event that an EJB needs to intercept this exception, it still can implement a catch block. |
The javax.ejb package defines eleven exceptions. Those of most interest to J2EE developers are:
javax.ejb.EJBException
This is a convenient runtime wrapper exception intended for wrapping unrecoverable checked exceptions. We may use it to wrap fatal exceptions such as JDBC exceptions that must be caught. However, there's a strong case that any exception that we define ourselves that must be wrapped in an EJBException and rethrown should simply be an unchecked (runtime) exception. This will simplify EJB code. As the catch block that throws an EJBException doesn't need to perform any cleanup (the EJB container will roll back the transaction and discard the bean instance), catching and rethrowing the exception adds no value.
The following are standard EJB app exceptions. They are reported to clients:
javax.ejb.CreateException and subclasses
This should be thrown from an ejbCreate() method if the failure to create the bean should be viewed as an app error. For example, a stateful session bean ejbCreate() method might throw this exception if it was passed invalid arguments by a client. If the failure to create a bean reflects a system exception, a runtime exception should be used (the EJBException wrapper exception if necessary). For example, there's no point in telling a remote client that a table is missing in a relational database. The remote client shouldn't even know that the EJB tier uses a relational database.
EJBs are normally transactional components. As we saw in , one of the strongest arguments for using session EJBs to implement business logic is the option of using declarative Container Managed Transactions (CMT). We specify CMT behavior in EJB deployment descriptors, causing the container to create transactions if necessary for EJB method invocations, freeing the developer from the need to use JTA directly. This is an important productivity gain. However, it's important to understand exactly how CMT works. There is an overhead associated with creating a transaction, so we don't want to create transactions unnecessarily. Yet, transactions are vital to the integrity of business logic that involves multiple updates of transactional resources. Focusing on session beans with CMT, let's consider the transaction attributes allowed by the deployment descriptor and how they affect the behavior of our code. (As we saw in we should always prefer CMT to BMT if we have a choice.) The EJB specification recognizes six transaction attributes, which are associated with EJB methods in the ejb-jar.xml deployment descriptor. The following table summarizes the behavior each confers:
Transaction attribute as
|
Meaning for the method so marked
|
Effect if a caller already
|
Notes |
Recommended use |
---|---|---|---|---|
|
||||
Required |
The method is guaranteed to run in a JTA transaction. If the caller has a transaction, it will be used. If not, a new transaction will be created. |
Method m will run in the caller's transaction. |
If the caller's transaction is rolled back, the work of method m will be rolled back automatically. |
This should be the default transaction attribute for session bean methods, which define the public interface of the EJB tier. |
This is the most flexible option. It allows the caller to use method m as a piece of a larger, reversible, unit of work. Yet method m can still be used independently. For example, it is able to support calls from clients outside the EJB tier. |
||||
RequiresNew |
The method is guaranteed to run in a new transaction, whether or not the caller has a transaction. |
The caller's transaction is suspended during execution of the method. |
This can be dangerous. If method m will not be reversed. This means that the caller does not have overall control of the business operation.
|
Only useful in the unusual case that method m implements a distinct piece of work that must commit regardless of the success of any higher-level operation in progress. An example might be an auditing action, where a persistent record must be made of the fact that a user attempted a certain action, regardless of its success. |
NotSupported |
The method is assumed not to be interested in JTA transactions, and will execute without a transaction context. If the method invokes other EJBs or resource managers, no transaction context will be propagated. |
The caller's transaction is suspended during execution of the method. |
Rollback of the caller's transaction will have no effect on any changes made by method m.
|
Appropriate if the method's implementation is non-transactional.
|
Supports |
If the caller has a transaction, it will be propagated to the method, and execution will occur as in the Required case. If the caller does not have a transaction, the container will not create one, and the method will execute without one, as in the NotSupported case. |
Method m will run in the caller's transaction, as in the Required case. |
Potentially dangerous. The semantics of method m will vary depending on whether the client has a transaction context.
|
Don't use this transaction attribute for updates. In the case of read-only operations, it can be used safely way as NotSupported. |
Mandatory |
The method requires a JTA transaction, which must be provided by the client. If the client does not have a transaction context when calling the method, the container throws a
|
This is the only valid case. |
Often an alternative to Requires. Limits the situations in which the method may be called. However, this may be a good thing. |
Useful for entity bean methods when using a session façade. In this case, we want the client to work through the façade, rather than manipulate low-level components such as entity beans, possibly coming in under the radar of our business logic and breaking system integrity.
|
Never |
The opposite of Mandatory. The method must not be called by a client with a transaction context. If it is, the container throws a java.rmi.RemoteException. Within the method, behaviour will be the same as in the NotSupported case. |
Not permitted. |
An alternative to NotSupported if it is considered essential to prevent a client with a transaction context calling the method. Not available to entity beans. |
Important |
The safest EJB transaction attributes are Required (for session beans) and Mandatory (for entity beans). These attributes ensure the expected transaction behavior, regardless of the caller's transaction context A transaction attribute of NotSupported can be used to boost the performance of read-only methods, for which transactions are not required. This situation arises often, as web apps frequently use read-only data. |
Note |
The EJB specification suggests that an "app Assembler", distinct from the EJB developer, may set transaction attributes. This is potentially dangerous. As we've seen, changing the transaction attributes of an EJB method can change the method's semantics. Only if the bean developer defines the transaction attributes can they develop meaningful unit tests for EJBs. While the removal of the specification of EJB transaction attributes from Java code is desirable, their removal from the control of the bean developer is misguided. |
Although the details of transaction management are best left to the EJB container, deciding whether a transaction should be rolled back is usually a matter of business logic (remember that if there's a runtime exception, the EJB container will automatically ensure rollback, whether a bean uses BMT or CMT). Accordingly, it's easy to ensure rollback in a bean using CMT.
All EJBs have access to an instance of the EJB context interface, supplied at run time by the container. There are three subinterfaces of javax.ejb.EJBContext, available at run time to the three kinds of EJB: javax.ejb.SessionContext, javax.ejb.EntityContext, and javax.ejb.MessageDrivenContext. The EJBContext superinterface offers a setRollbackOnly() method that allows CMT beans to mark the current transaction irreversibly for rollback.
As the EJB container, not the bean developer, provides the implementation of an EJB's component interface (the local or remote interface), how do we ensure that the component interface and bean implementation class remain in synch? Lack of such synchronization is a common cause of problems at deployment time, so this is an important issue, unless we are prepared to become dependent on tool support for EJB development. We can choose to make the bean implementation class implement the component interface, but this has the following disadvantages:
As the component interface must extend either javax.ejb.EJBObject or javax.ejb.EJBLocalObject, the bean implementation will be forced to provide empty implementations of methods from these interfaces that will never be invoked by the container. This is confusing to readers.
Fortunately, there is a simple and effective solution, which is widely recognized: to have both component interface and bean implementation class implement a common "business methods" interface. The business methods interface does not extend any EJB infrastructure interfaces, although it may extend other app business interfaces. If we're dealing with an EJB with a remote interface, each method in the business methods interface must be declared to throw javax.remote.RemoteException. In the case of EJBs with remote interfaces, the business methods interface is normally an EJB-tier implementation detail: clients will work with the bean's remote interface, not the business methods interface. In the case of EJBs with a local interface, the business method interface's methods may not throw RemoteException, which means that the business methods interface need not be EJB-specific. Often the business methods interface, rather than the EJB's local interface, is of most interest to clients of the EJB. In the sample app, for example, the com.wrox.expertj2ee.ticket.boxoffice.BoxOffice interface is an ordinary Java interface, which could be implemented without using EJB. Clients invoke methods on this interface, rather than the EJB's local interface, which extends it. Use of a "Business Methods Interface" isn't really a design pattern – it's more a workaround for an inelegant feature of the EJB specification. But as it's often referred to as a pattern, I'm following established terminology. Let's look at how we use this pattern for the sample app's BoxOffice EJB, and the naming conventions used. Using this pattern involves the following four classes for the EJB:
BoxOffice
The interface that defines the business methods on the EJB, extended by the component interface and implemented by the bean implementation. In the sample app, this interface is defined apart from the EJB implementation and is used by clients. Even if clients will work only with the EJB's local or remote interface, such an interface can be introduced purely as an EJB tier implementation choice to synchronize the classes required to implement the bean.
The following UML class diagram illustrates the relationship between these classes and interfaces and those required by the EJB specification. The business methods interface is circled. Note that the bean implementation class, BoxOfficeEJB, extends a generic superclass, com.interface21.ejb.support.AbstractionStatelessSessionBean, that helps to meet the requirements of the EJB specification. We'll discuss this and other EJB support classes when we look at app infrastructure in the next chapter:
The following is a complete listing of the BoxOffice EJB's local interface, which defines no new methods:
public interface BoxOfficeLocal extends javax.ejb.EJBObject, com.wrox.expertj2ee.ticket.boxoffice.BoxOffice { }
The following shows how the bean's implementation class implements the business interface as well as the javax.ejb.SessionBean interface required by the EJB specification:
public class BoxOfficeEJB extends AbstractStatelessSessionBean implements SessionBean, BoxOffice { // Implementation omitted }
Note |
Although the EJB Business Methods Interface is widely advocated, not everyone agrees on naming conventions. Some sources advocating using a BusinessMethods suffix for the name of the business methods interface. Whatever naming convention you decide to adopt, use it consistently in all your EJB implementations to avoid confusion. |
The Business Methods Interface pattern is a simple technique that can save a lot of time. However, it can't detect problems in synchronization between an EJB's home interface and implementation class. For example, home interface create() methods must correspond to ejbCreate() methods. Problems synchronizing between home interface and bean implementation class may produce mysterious "abstract method errors" in some servers. The generic superclasses discussed in the next chapter help to avoid such errors, where stateless session beans and message-driven beans are concerned.
Important |
Use the Business Methods Interface pattern to help avoid problems with keeping component interface and business methods in sync. Errors will be picked up at compile time, rather than when the beans are deployed to an EJB container. Although it may be possible to synchronize these classes with an IDE, the business methods interface uses the compiler, and is thus guaranteed to work whatever the development environment. |