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.