Transactions with EJB 2.0

This example lets Hogwarts students sign up for classes online. We're using two CMP beans, Student and Course, to store the Student and Course entities. We're also using a Session Bean to act as a facade to the entity beans.

This example uses the following schema:


    CREATE TABLE transaction_student (
      id INTEGER NOT NULL,
      name VARCHAR(250) NOT NULL,
      password VARCHAR(250) NOT NULL,
      gender VARCHAR(6) NOT NULL,

      PRIMARY KEY(name)
    );

    CREATE TABLE transaction_course(
      id INTEGER NOT NULL,
      name VARCHAR(250) NOT NULL,
      room VARCHAR(250) NOT NULL,
      teacher VARCHAR(250) NOT NULL,
      max_student_amount INTEGER,

      PRIMARY KEY(id)
    );

    CREATE TABLE transaction_student_course_mapping (
      transaction_student VARCHAR(250) NOT NULL,
      transaction_course VARCHAR(250) NOT NULL
    );
  

Session Beans

Unlike CMP beans, session beans don't represent entities. They contain business logic and are typically used to group calls to CMP beans, presenting the Client with a simple API. Using a session bean facade keeps our Client servlet clean because much of the business logic can be put inside the session bean. Furthermore, using a session bean facade allows us to take advantage of Resin-CMPs inbuilt transaction management.

Session Beans have the same general structure as CMP beans. They consist of a Home Interface, a Local Interface, and an Implementation Class. Note that the implementation class needs to implement javax.ejb.SessionBean. In Resin-CMP, you can simply extend com.caucho.servlet.http.AbstractSessionBean, which provides an empty implementation of the SessionBean interface.

Session beans are defined in the deployment descriptor within a <session> block. We have set the <session-type> value to Stateful to indicate that we want one dedicated Session Bean instance for each client. This lets us save data inside the bean between method calls.
We could have declared our bean to be stateless. Stateless Session beans are more efficient because they can be pooled. But stateless session beans are not as convenient because they cannot store data between client requests.


 <session>
  <ejb-name>transaction_registration_session</ejb-name>
  <local-home>example.cmp.transaction.RegistrationSessionHome</local-home>
  <local>example.cmp.transaction.RegistrationSession</local>
  <ejb-class>example.cmp.transaction.RegistrationSessionBean</ejb-class>
  <session-type>Stateful</session-type>
  <transaction-type>Container</transaction-type>
 </session>
  

EJB Transactions

Methods in Session and CMP beans are configured individually for Resin-CMPs automatic transaction support. Transaction management setup is performed in the deployment descriptor's <assembly-descriptor> section. For the transaction_registration_session bean, we set the finalizeRegistration method's <trans-attribute> to "Required". This means that the finalizeRegistration() method will always be part of a transaction.

All other methods in the transaction_registration_session bean are exempted from Resin-CMP's transaction management because their <trans-attribute> property is set to "Never". This results in a small performance gain because the container has less work to do for these methods.

The <assembly-descriptor> can be ommitted in Resin-CMP. The container defaults to setting the "Required" attribute for all methods in all EJBs.


 <assembly-descriptor>
  <container-transaction>
   <method>
    <ejb-name>transaction_registration_session</ejb-name>
    <method-intf>Remote</method-intf>
    <method-name>*</method-name>
   </method>
   <trans-attribute>Never</trans-attribute>
  </container-transaction>
  <container-transaction>
   <method>
    <ejb-name>transaction_registration_session</ejb-name>
    <method-intf>Remote</method-intf>
    <method-name>finalizeRegistration</method-name>
   </method>
   <trans-attribute>Required</trans-attribute>>
  </container-transaction>
  ...
 </assembly-descriptor>
  

Security

The latest EJB 2.0 draft specification does not specify how the container may pass security credentials to its beans. Containers have to come up with their own schemes. Resin-CMP obtains a user Principal object during servlet authentication via Resin Core's Authenticator mechanism. This Principal is automatically passed to any EJBs that your servlet or jsp clients may use.

The servlet's security is set up in web.xml. Resin's authentication system retains a Principal for each successfully logged in user.


   <login-config auth-method='form'>
     <form-login-config form-login-page='/login.xtp'
                        form-error-page='/login_error.xtp'/>
     <authenticator id='example.cmp.transaction.CMPAuthenticator'/>
   </login-config>
   <security-constraint
    url-regexp='/servlet/example.cmp.transaction.RegistrationServlet'
    role-name='student'/>
  

In RegistrationSessionBean's ejbCreate() method, we can obtain this Principal from the SessionContext:


  student = (Student) ctx.getCallerPrincipal();
  

Resin-CMP makes sure that the SessionContext knows about the caller Principal that was created during servlet authentication.

The example

When first called during an HttpServletSession, the RegistrationServlet obtains an instance of the RegistrationSession bean. RegistrationSession is a stateful session bean, dedicated to the current client. The servlet saves a reference in its HttpServletSession object so that we can work with the same Session Bean during subsequent web requests.

The RegistrationSession bean lets the user select any number of courses and saves the current selections in a Collection object. Selecting and deselecting courses does not cause any interaction with the database.

The finalizeRegistration() method tries to commit the selected courses to the database. If this succeeds for all courses, the transaction has committed and is successful. If one of the courses cannot be submitted for any reason, all previous course submission to the database are rolled back and the transaction has failed.

We notify Resin-CMP by calling SessionContext.setRollbackOnly() that the transaction has failed. Resin then rolls back any previous course submissions.

Our RegistraionSessionBean.java implementation class implements the javax.ejb.SessionSynchronization interface. One of the three methods defined by this interface is afterCompletion(boolean committed). We can use this method to perform any task based on the outcome of a transaction. For example, in this example, we could send an email notification whenever a new schedule has been successfully committed.