Special Edition Using Enterprise JavaBeans 2.0
Exceptions and Transactions
The most important reason to talk about exception handling in EJB is to understand how exceptions impact transactions. You've already seen some of the reasons why it's important to separate application and system exceptions, but their effect on transactions is at the top of the list. An enterprise system must handle exceptions in such a way that transactional integrity is maintained . This requirement places responsibility on both the EJB container and on you as a bean provider. Throwing an Application Exception During a Transaction
If an application exception is thrown from a bean method during a transaction, the container doesn't automatically roll back the transaction or mark it for rollback. The intent is to give the client an opportunity to recover from a business logic error. This hands-off approach taken by the container gives you flexibility in responding to exceptions, but there's always a catch. In this case, it's the added responsibility you're given to make sure that a transaction commit doesn't result in an inconsistent state. Let's first look at what this requires when you're using container-managed transaction demarcation for an entity bean or session bean. Message-driven beans can't throw application exceptions, so they can be ignored for now. When the container is managing transaction demarcation, a transaction can be started by the caller of your bean method or by the container itself. Running within the context of the caller's transaction implies that a transaction attribute of Required , Mandatory , or Supports was assigned to your method. If the RequiresNew attribute is assigned or if Required is assigned and the caller doesn't provide a transaction context, the container starts the transaction that applies to the method call. The only difference this makes for the discussion here is that it determines who commits the transaction and when that commit is attempted. When a bean method executing under a caller's transaction context throws an application exception, the container simply rethrows the exception to the client. Actually, no matter how the transaction is being managed, an application exception is always passed on to the client by the container. This is important because it satisfies a goal for EJB to always report application exceptions directly to the client. Where the container's response to an application exception can differ slightly relates to transaction rollback. When a caller's transaction context applies, the container doesn't roll back the transaction when an application exception is thrown. This gives the client the option to continue working and eventually attempt a commit. The client can continue calling the bean instance that threw the exception as well. This might sound a little dangerous, but if you carry out your responsibilities as a bean provider, it's completely safe.
Note You'll see later that the container discards a bean instance when a system exception occurs. An important distinction to notice here is that this is never the case with an application exception. This type of exception represents a business logic issue, which is never so severe as to require such a precaution. Because application exceptions are checked exceptions, you can, in some ways, consider them "expected." The container responds to the unexpected exceptions more cautiously.
Allowing the client to continue working under a transaction after an application exception is thrown requires that you ensure that data integrity will not be lost if the transaction is eventually committed. In practical terms, this means that if at all possible, you should determine the need for throwing an application exception and throw it before you update the state of your bean instance or allow it to operate on the state of any other object. For example, if invalid parameters are passed to an ejbCreate or business method, you should throw an application exception to report that fact before doing anything else. Similarly, if you want to veto the removal of an entity object by throwing a RemoveException , you should do so before deleting any data from the database. There will of course be cases where it isn't possible to determine the need for reporting an exception until data has been changed. If these changes cannot be completely reversed before throwing the exception, your only choice is to mark the current transaction for rollback by calling the setRollbackOnly method defined as part of EJBContext . By ensuring that the transaction cannot be committed, you can guarantee that data consistency will be restored when the transaction is rolled back. If a client continues to work under its transaction context after receiving an application exception, it can attempt to commit the transaction as usual. If the transaction was marked for rollback by the bean method that threw the exception, the commit is guaranteed to fail and a rollback will occur. Otherwise, the commit is allowed to proceed and will only fail if there is some other problem unrelated to the exception that was thrown. To prevent wasted effort, your EJB clients should do what they can to determine if a transaction has been marked for rollback before attempting to continue on after an exception. If a client is an EJB using container-managed transaction demarcation, you should call getRollbackOnly on its EJBContext to check the status. Otherwise, if it's an EJB using bean-managed demarcation or some other client type, you should call getStatus on the current UserTransaction before proceeding.
Tip It's important for a client to be able to determine if a transaction is marked for rollback programmatically, but it can also be useful to include this information as part of your bean documentation. As part of documenting the application exceptions thrown by a bean method, you should include whether the associated transaction is marked for rollback before a particular exception is thrown.
The exception handling process is slightly different when the container starts the transaction that applies to a bean method when the method is invoked. Remember that a transaction started by the container to support a bean method is also completed by the container as soon as the method exits. This means that the commit or rollback occurs before the result of the method call is returned to the client. If a bean method marks a transaction for rollback and throws an application exception in this situation, the container performs the rollback as soon as the method call completes. If the transaction isn't marked for rollback, the container attempts to commit it. In either case, the container rethrows the application exception to the client to report the error. If the client is running under its own transaction context ( implying that the bean method in this case was deployed using RequiresNew as its transaction attribute), the client's transaction is unaffected by the exception or the rollback, if one was performed. The diagram in Figure 13.1 summarizes the behavior specified for application exceptions relative to container-managed transaction demarcation. Note that it includes the behavior that applies when the transaction context is unspecified. This can occur when a transaction attribute of NotSupported , Never , or Supports is used. Here the container performs no management of a transaction, so an application exception is simply rethrown to the client. Figure 13.1. Application exception handling for container-managed transaction demarcation.
As discussed in Chapter 12, using container-managed demarcation is the recommended approach for managing transaction boundaries. As it turns out, most of the discussion that's needed about exception handling and transactions applies to this option. As you saw previously in Chapter 12, you can also implement the management of transactions for session and message-driven beans yourself. In this case, a bean method that requires a transaction runs within a transaction started by the bean itself, so the effect of an exception is straightforward. Prior to throwing an application exception, a session bean method using bean-managed transaction demarcation is responsible for committing or rolling back the current transaction. Once the exception is thrown, the container rethrows it to the client.
Note A stateful session bean can keep a transaction open across method calls, so it isn't strictly necessary to commit or roll back a transaction for bean-managed transaction demarcation when an exception occurs within this type of bean. However, the state of the bean must always be managed in such a way that a commit is not performed for invalid data. If another method is eventually responsible for the decision to commit or roll back, you have to ensure data consistency prior to throwing an application exception or make that method aware of the exception that occurred.
The following points summarize the effects of application exceptions on transactions:
Throwing a System Exception During a Transaction
If a system exception is thrown from a bean method during a transaction, the container logs the error, discards the bean instance that threw the exception, and either rolls back the transaction or marks the transaction for eventual rollback by the client. Where the primary goal for handling an application exception is to provide the opportunity for the client to recover from the problem, the primary goal after a system exception is to protect the integrity of the data involved and clean up the mess left behind. System exceptions represent more serious problems than application exceptions do, but the good news is that the container takes full responsibility for reacting to them as far as transactions are concerned . When a system exception occurs, the container makes no assumptions that the bean instance involved was left in a valid state or that the associated transaction had yet to make any data changes. Instead the container discards the bean instance and guarantees that no other calls will be made to it. As pointed out earlier in the chapter, this only causes a problem for stateful session beans because any associated client state is lost when an instance is discarded. The client will have to reestablish a session when this occurs and rebuild the conversational state. As a bean provider, you have no decision to make about rolling back a transaction or not when a system exception is thrown. The container makes the decision for you by always forcing a rollback. This precaution is what allows you to let uncaught instances of RuntimeException be thrown to the container or to throw your own instances of EJBException to report subsystem or other unexpected errors. As with application exceptions, system exceptions are handled somewhat differently based on whether the caller provided the transaction context or it was started by the container. In either case, the bean instance is discarded and the exception is logged. The EJB specification requires that the server be able to log errors, but it doesn't specify how. You'll need to look at the documentation for a particular application server to see how that vendor decided to satisfy this requirement. If a caller provides the transaction context and a system exception is thrown from an entity or session bean using container-managed transaction demarcation, the container marks the transaction for rollback and throws either a TransactionRolledbackException or a TransactionRolledbackLocalException to the client. This makes it quite obvious that there is no point in continuing the work in progress and attempting to commit the transaction. If the transaction is instead started by the container, the container automatically rolls back the transaction and throws either a RemoteException (remote client) or an EJBException (local client). If the client is operating under its own transaction context in this situation, the rollback might or might not affect that transaction. The container can leave the client transaction alone, or mark it for rollback if data integrity is in doubt. Operation under an unspecified transaction context is similar in that a RemoteException (or EJBException if the client is local) is thrown to report a system error, and the container optionally can mark the client transaction for rollback. Unlike application exceptions, the container needs to step in when a session bean using bean-managed transaction demarcation throws a system exception. Because system exceptions indicate unexpected errors, the container can't assume that the bean will be left in a consistent state when one occurs. If a system exception is thrown while bean-managed demarcation is being used, the container logs the error, discards the instance, and throws a RemoteException or EJBException similar to how an exception is handled for container-managed demarcation. The most important behavior in this case is that the container will step in and roll back a transaction being managed by the bean if there is a transaction in progress that has not completed at the time a system exception is thrown. Figure 13.2 summarizes the behavior specified for handling system exceptions thrown from entity or session beans. Figure 13.2. The container handles system exceptions automatically for entity and session beans.
The container handles system exceptions thrown from message-driven beans a little differently than it does those from entity and session beans. This is because a message-driven bean has no client to throw an exception to when one occurs. Other than the fact that the container doesn't throw an exception to a client for a message-driven bean, its behavior when a system-level exception needs to be reported is the same. When a system exception occurs in a message-driven bean, the error is logged and the bean instance is discarded by the container. If a transaction was started by the container, that transaction is rolled back. If the bean is using bean-managed demarcation and has started a transaction that has not been committed before the exception occurs, the container will roll back that transaction. The following points summarize the effects of system exceptions on transactions:
|