The benefits of using transactions to group database updates are clear. You can easily code methods in a single component to implement transactions that run against a single data source. However, those methods may in turn be executed by another component, which itself is defining a transaction. In this situation, error recovery becomes difficult. For example, consider the following scenario in which an Enrollment component calls both Registrar and Billing components:
In the following figure, the Enrollment.enroll() method calls methods in the Registrar and StudentBilling components:
Registar.reserveSeat() checks that a seat is available. If so, it decrements the count of available seats and adds the student to the course’s enrollment list. If no seats are available, reserveSeat() fails.
StudentBilling.addToBill() checks that the student has a billable credit record. If so, addToBill() adds the course cost to the student’s bill for that semester. If the student has a credit problem (if, for example, she owes money for an overdue book), addToBill() fails.
Figure 5-2: An example EJB Server transaction
To be correct, both the database update made by the Registrar and the update made by the StudentBilling components must occur, or neither must occur. In other words, if the student cannot be billed, the course’s available seats must not be changed. To handle this case, you could add logic to the enroll() method to undo changes (requiring an unreserveSeat() method in Registrar). However, as more components are added to the scenario, the logic needed to undo previous changes quickly becomes unmanageable. It is much easier to define all the participating components to use EJB Server transactions. Then an error in any component can induce a rollback of all changes made by the other participating components before the error occurred.
By defining the participating components to use EJB Server transactions, you can be sure that the work performed by the components that participate in a transaction occurs as intended.