JBoss.org Community Documentation

Chapter 6. Introduction to Callbacks and Callback Handlers in EJB3

The EJB 3.0 specification defines some callbacks, and allows you to handle these by implementing your own callback handlers. The callbacks defined for each bean are shown below:
  • Stateless session bean callbacks

    • PostConstruct - is invoked when the bean is first created, after any dependency injection is done.
    • PreDestroy - is invoked when the bean is removed from the pool or destroyed.

  • Message-driven bean callbacks

    • PostConstruct - is invoked when the bean is first created, after any dependency injection is done.
    • PreDestroy - is invoked when the bean is removed from the pool or destroyed.

  • Stateful session bean callbacks

    • PostConstruct - is invoked when the bean is first created, after any dependency injection is done.
    • PreDestroy - is invoked when the bean is removed from the pool or destroyed. It will happen before any @Remove annotated method is invoked.
    • PostActivate
    • PrePassivate

  • Entity bean callbacks

    • PrePersist - Is invoked right before the entity is created in the database. Will cascade to all entities to which this operation is cascaded.
    • PostPersist - Is invoked right after the entity is created in the database. Will cascade to all entities to which this operation is cascaded.
    • PreRemove - Is invoked right before the entity is deleted in the database. Will cascade to all entities to which this operation is cascaded.
    • PostRemove - Is invoked right after the entity is deleted in the database. Will cascade to all entities to which this operation is cascaded.
    • PreUpdate - Takes place right before the database is updated.
    • PostUpdate - Takes place immediately after the database has been updated.
    • PostLoad - Takes place right after data has been loaded from the database and associated with the entity

    The callbacks are not compulsory, and you can define the ones you want to handle.

Using callbacks on the bean class:

You use the callbacks listed above by annotating methods in the bean class. The annotations live in the javax.ejb package so for example PostConstruct would be javax.ejb.PostConstruct. You can call the methods what you like, but their method signature must return a void and take no arguments. Look at the org.jboss.tutorial.callback.bean.CustomerDAOBean stateless session bean and you will see that the preDestroyCallback() method has been annotated with @javax.ejb.PreDestroy. For session/message-driven beans, just like for interceptors, if the bean class extends another class any callback annotated methods on the super class will be invoked first.

Entity listeners for entity beans:

You can also separate out the callbacks for entity beans into a separate EntityListener class. Take a look at the org.jboss.tutorial.callback.bean.Customer entity bean, and you will see that the class has been annotated with @javax.persistence.EntityListener("org.jboss.tutorial.callback.bean.CustomerCallbackListener"). This specifies that the org.jboss.tutorial.callback.bean.CustomerCallbackListener class should be used as the callback listener class for the bean. Now open org.jboss.tutorial.callback.bean.CustomerCalbackListener and you will see that the class annotates the callback methods in the same way as when defining callbacks on the bean class itself. However, one __important difference__ is that callback methods defined in a listener class must take a single argument, which will be the bean we are working with. The parameter must be of type java.lang.Object, or the actual bean type.

Interceptors for session/message-driven beans:

Callbacks for session beans can also be put into a separate class, configured as an interceptor. This means that your interceptors can initialise themselves when constructed. The lifecycle methods in an interceptor must have the following signature:

Object <any method name>(InvocationContext)

org.jboss.tutorial.callback.bean.CustomerDAOBean specifies that it wants to use an external interceptor.

@Stateless
@Remote(CustomerDAO.class)
@Interceptors({LifecycleInterceptor.class})
public class CustomerDAOBean implements CustomerDAO
{
   ...
}
			

and org.jboss.tutorial.callback.bean.LifecycleInterceptor has a @PostConstruct annotated method. As shown, each interceptor lifecycle method must call proceed on the InvocationContext to invoke the next interceptor. Interceptors may contain both the @AroundInvoke methods for intercepting business methods, and lifecycle methods. If you want to configure lifecycle methods for your interceptors with xml, you will need to do this in the interceptor section of ejb-jar.xml, and the keywords are post-construct-method, post-activate-method, pre-passivate-method and pre-destry-method. For example:

<ejb-jar>
   <interceptors>
      <interceptor>
         <interceptor-class>org.acme.SomeInterceptor</interceptor-class>
         <around-invoke-method>
            <method-name>interceptorMethod</method-name>
         </around-invoke-method>
         <post-construct-method>
            <method-name>sendCancelMessage</method-name>
         </post-construct-method>
         <pre-destroy-method>
            <method-name>sendCancelMessage</method-name>
         </pre-destroy-method>
      </interceptor>
   </interceptors>
</ejb-jar>

			

Interceptor methods for handling lifecycle events follow exactly the same ordering and inheritance rules as business method interceptor methods, and an interceptor instance follows the lifecycle of the bean instance it is bound to.

Building and Running

From the command prompt, move to the "callbacks" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”

Ant Users:

Make sure your JBossAS-5.x is running

$ ant
$ ant run

run:
     [java] Create Bill Burke and Monica Smith
     [java] Bill and Monica get married
     [java] Get all the Burkes
     [java] There are now 2 Burkes
     [java] Bill and Monica are moving abroad
	

Maven Users: Make sure the AS is not running.

$ mvn clean install -PRunSingleTutorial
	

In the jboss console window you should see:

23:52:11,162 INFO  [STDOUT] LifecycleInterceptor postConstruct
23:52:11,162 INFO  [STDOUT] PostConstruct - Have EntityManager: true
23:52:11,424 INFO  [STDOUT] -- CustomerDAOBean.create()
23:52:11,575 INFO  [STDOUT] doPrePersist: About to create Customer: Bill Burke
23:52:11,608 INFO  [STDOUT] doPostPersist: Created Customer: Bill Burke
23:52:11,644 INFO  [STDOUT] -- CustomerDAOBean.create()
23:52:11,644 INFO  [STDOUT] doPrePersist: About to create Customer: Monica Smith
23:52:11,645 INFO  [STDOUT] doPostPersist: Created Customer: Monica Smith
23:52:11,655 INFO  [STDOUT] -- CustomerDAOBean.find()
23:52:11,673 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Smith
23:52:11,700 INFO  [STDOUT] -- CustomerDAOBean.merge()
23:52:11,704 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Smith
23:52:11,705 INFO  [STDOUT] doPreUpdate: About to update Customer: Monica Burke
23:52:11,727 INFO  [STDOUT] doPostUpdate: Updated Customer: Monica Burke
23:52:11,735 INFO  [STDOUT] -- CustomerDAOBean.findByLastName(id)
23:52:12,101 INFO  [STDOUT] doPostLoad: Loaded Customer: Bill Burke
23:52:12,102 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Burke
23:52:12,113 INFO  [STDOUT] -- CustomerDAOBean.delete()
23:52:12,116 INFO  [STDOUT] doPostLoad: Loaded Customer: Bill Burke
23:52:12,118 INFO  [STDOUT] doPreRemove: About to delete Customer: Bill Burke
23:52:12,124 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Burke
23:52:12,125 INFO  [STDOUT] doPreRemove: About to delete Customer: Monica Burke
23:52:12,128 INFO  [STDOUT] doPostRemove: Deleted Customer: Bill Burke
23:52:12,128 INFO  [STDOUT] doPostRemove: Deleted Customer: Monica Burke

	

Now if you open up the persistence.xml, in this tutorial, and uncomment the following:

<property name="hibernate.show_sql" value="true"/>
	

and rebuild and run the tutorial, the output should be:

00:00:24,514 INFO  [STDOUT] LifecycleInterceptor postConstruct
00:00:24,514 INFO  [STDOUT] PostConstruct - Have EntityManager: true
00:00:24,759 INFO  [STDOUT] -- CustomerDAOBean.create()
00:00:24,760 INFO  [STDOUT] doPrePersist: About to create Customer: Bill Burke
00:00:24,760 INFO  [STDOUT] Hibernate: insert into CUSTOMER (id, CITY, FIRST, LAST, STATE, STREET, ZIP) values (null, ?, ?, ?, ?, ?, ?)
00:00:24,761 INFO  [STDOUT] Hibernate: call identity()
00:00:24,762 INFO  [STDOUT] doPostPersist: Created Customer: Bill Burke
00:00:24,773 INFO  [STDOUT] -- CustomerDAOBean.create()
00:00:24,773 INFO  [STDOUT] doPrePersist: About to create Customer: Monica Smith
00:00:24,774 INFO  [STDOUT] Hibernate: insert into CUSTOMER (id, CITY, FIRST, LAST, STATE, STREET, ZIP) values (null, ?, ?, ?, ?, ?, ?)
00:00:24,774 INFO  [STDOUT] Hibernate: call identity()
00:00:24,775 INFO  [STDOUT] doPostPersist: Created Customer: Monica Smith
00:00:24,784 INFO  [STDOUT] -- CustomerDAOBean.find()
00:00:24,785 INFO  [STDOUT] Hibernate: select customer0_.id as id8_0_, customer0_.CITY as CITY8_0_, customer0_.FIRST as FIRST8_0_, customer0_.LAST as LAST8_0_, customer0_.STATE as STATE8_0_, customer0_.STREET as STREET8_0_, customer0_.ZIP as ZIP8_0_ from CUSTOMER customer0_ where customer0_.id=?
00:00:24,786 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Smith
00:00:24,817 INFO  [STDOUT] -- CustomerDAOBean.merge()
00:00:24,818 INFO  [STDOUT] Hibernate: select customer0_.id as id8_0_, customer0_.CITY as CITY8_0_, customer0_.FIRST as FIRST8_0_, customer0_.LAST as LAST8_0_, customer0_.STATE as STATE8_0_, customer0_.STREET as STREET8_0_, customer0_.ZIP as ZIP8_0_ from CUSTOMER customer0_ where customer0_.id=?
00:00:24,818 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Smith
00:00:24,818 INFO  [STDOUT] doPreUpdate: About to update Customer: Monica Burke
00:00:24,820 INFO  [STDOUT] Hibernate: update CUSTOMER set CITY=?, FIRST=?, LAST=?, STATE=?, STREET=?, ZIP=? where id=?
00:00:24,820 INFO  [STDOUT] doPostUpdate: Updated Customer: Monica Burke
00:00:24,834 INFO  [STDOUT] -- CustomerDAOBean.findByLastName(id)
00:00:24,880 INFO  [STDOUT] Hibernate: select customer0_.id as id8_, customer0_.CITY as CITY8_, customer0_.FIRST as FIRST8_, customer0_.LAST as LAST8_, customer0_.STATE as STATE8_, customer0_.STREET as STREET8_, customer0_.ZIP as ZIP8_ from CUSTOMER customer0_ where customer0_.LAST=?
00:00:24,881 INFO  [STDOUT] doPostLoad: Loaded Customer: Bill Burke
00:00:24,881 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Burke
00:00:24,896 INFO  [STDOUT] -- CustomerDAOBean.delete()
00:00:24,897 INFO  [STDOUT] Hibernate: select customer0_.id as id8_0_, customer0_.CITY as CITY8_0_, customer0_.FIRST as FIRST8_0_, customer0_.LAST as LAST8_0_, customer0_.STATE as STATE8_0_, customer0_.STREET as STREET8_0_, customer0_.ZIP as ZIP8_0_ from CUSTOMER customer0_ where customer0_.id=?
00:00:24,897 INFO  [STDOUT] doPostLoad: Loaded Customer: Bill Burke
00:00:24,898 INFO  [STDOUT] doPreRemove: About to delete Customer: Bill Burke
00:00:24,898 INFO  [STDOUT] Hibernate: select customer0_.id as id8_0_, customer0_.CITY as CITY8_0_, customer0_.FIRST as FIRST8_0_, customer0_.LAST as LAST8_0_, customer0_.STATE as STATE8_0_, customer0_.STREET as STREET8_0_, customer0_.ZIP as ZIP8_0_ from CUSTOMER customer0_ where customer0_.id=?
00:00:24,899 INFO  [STDOUT] doPostLoad: Loaded Customer: Monica Burke
00:00:24,899 INFO  [STDOUT] doPreRemove: About to delete Customer: Monica Burke
00:00:24,900 INFO  [STDOUT] Hibernate: delete from CUSTOMER where id=?
00:00:24,900 INFO  [STDOUT] doPostRemove: Deleted Customer: Bill Burke
00:00:24,900 INFO  [STDOUT] Hibernate: delete from CUSTOMER where id=?
00:00:24,900 INFO  [STDOUT] doPostRemove: Deleted Customer: Monica Burke


	

Thus you can see how the callbacks on the entity bean wrap the interaction with the database.