This tutorial is meant for EJB3 application developers on JBoss Application Server. The tutorial walks you through the EJB 3.0 features and how they deploy to JBoss. Please check the Chapter 1, Installation for system requirements.
You must set the JBOSS_HOME environment variable to run any of the tutorial examples.
Unix: $ export JBOSS_HOME=<where your jboss 5.x distribution is> Windows: $ set JBOSS_HOME=<where your jboss 5.x distribution is>
To run these examples you need JBossAS 5.0.1 GA or higher version (if available). JBossAS can be downloaded from here.
The tutorials can be built with either Ant (version 1.7) or Maven (version 2.0.9). Depending on which build tool you want to use, you will have to download it from their sites.
For easy reference throughout the tutorials, we use the EJB3_TUTORIAL_HOME which points to the top level folder where you downloaded the tutorials. Set it appropriately:
Unix: $ export EJB3_TUTORIAL_HOME=<where your EJB3 tutorial is> Windows: $ set EJB3_TUTORIAL_HOME=<where your EJB3 tutorial is>
It is very easy to create a Stateless Bean with EJB 3.0. All bean types are homeless in EJB 3.0 so all you have to do to
create a Stateless bean is to create a bean class and have it implement at least one interface. Take a look at
org.jboss.tutorial.stateless.bean.CalculatorBean
The first thing to notice is that the class is tagged as @Stateless
. This marks the class as a stateless bean and
the deployer will deploy that class as a stateless bean EJB container.
CalculatorBean also implements two interfaces. One is the remote interface of the EJB the other is the local interface.
Take a look at org.jboss.tutorial.stateless.bean.CalculatorRemote
.
To define this as the remote interface of Calculator bean
you either annotate the bean class and specify what the remote interfaces are, or you annotate each remote interface the bean class
implements with @javax.ejb.Remote. only need to annotate the bean class with @javax.ejb.Remote
.
Similar for org.jboss.tutorial.stateless.bean.CalculatorLocal
as you need to annotate the bean
class with @javax.ejb.Local
for it to be the local interface of the CalculatorBean.
The Calculator bean will have two JNDI bindings for the remote and Local interface. By default, JBoss will use ejbName/local and ejbName/remote for the local and remote interfaces, respectively.
Open up org.jboss.tutorial.stateless.client.Client
.
You'll see that it looks up the stateless bean under "ejbName/remote". Also notice that there is no Home interface and
you can begin executing on the stateless bean right away.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "stateless" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run
$ mvn clean install -PRunSingleTutorial
It is very easy to create a Stateful Bean with EJB 3.0. All bean types are homeless in EJB 3.0 so all you have to do to create a
Stateful bean is to create a bean class and have it implement at least one interface.
Take a look at org.jboss.tutorial.stateful.bean.ShoppingCartBean
in the tutorial.
The first thing to notice is that the class is tagged as @Stateful
. This marks the class as a stateful bean and
the deployer will deploy that class as a stateful bean EJB container.
ShoppingCartBean also implements a remote interface. Take a look at org.jboss.tutorial.stateful.bean.ShoppingCart
Take another look at org.jboss.tutorial.stateful.bean.ShoppingCartBean
.
Look for the method annotated as @Remove. Instead of explicitly calling EJBObject.remove() in your applications and thus polluting it
further with J2EE specific code, any method tagged with @Remove will cause the stateful bean instance to be removed from the container
at the end of the method call.
The ShoppingCartBean will have its remote interface bound in JNDI, by default, under the ejbName/local and/or ejbName/remote for the local and remote interfaces, respectively.
Open up org.jboss.tutorial.stateful.client.Client
.
You'll see that it looks up the stateful bean under "ejbName/remote". Also notice that there is no Home interface and you can begin
executing on the stateful bean right away. When you access the bean in JNDI, an instance of the stateful bean will be created on the server.
So, when you need a different instance of the stateful bean, you do an additional jndi.lookup() to get this new reference.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "stateful" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run
$ mvn clean install -PRunSingleTutorial
The EJB 3.0 specification has support for Blob and Clob types. The specification allows you to map the following types to an entity property:
java.sql.Blob
java.sql.Clob
Any Serializable Object
byte[], Byte[]
char[], String, Character[]
To use this feature just need to use the @javax.persistence.Lob
annotation. The Lob
annotation is an
encapsulation of what type of lob you want. Below is an example of defining fields in an entity that are blobs or clobs.
@Entity public class BlobEntity implements Serializable { private long id; private Blob blobby; private Clob clobby; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) public long getId() { return id; } public void setId(long id) { this.id = id; } @Lob @Basic(fetch = FetchType.EAGER) public Blob getBlobby() { return blobby; } public void setBlobby(Blob blobby) { this.blobby = blobby; } @Lob @Basic(fetch = FetchType.EAGER) public Clob getClobby() { return clobby; } public void setClobby(Clob clobby) { this.clobby = clobby; } }
Open up org.jboss.tutorial.blob.bean.LobTesterBean
and look for the create()
method. JBoss EJB3
is built on top of the Hibernate persistence engine. Hibernate has some helper methods for creating blobs and clobs that {{LobTesterBean}}
uses.
org.hibernate.Hibernate.createBlob(byte[] bytes)
org.hibernate.Hibernate.createBlob(InputStream stream, int length)
org.hibernate.Hibernate.createBlob(InputStream stream)
org.hibernate.Hibernate.createClob(String string)
org.hibernate.Hibernate.createClob(Reader reader, int length)
Blobs and clobs must only be accessed within a transaction. Blobs and clobs are also not serializable or detachable.
This is pretty easy, just look at BlobEntity2.java
@Entity public class BlobEntity2 implements Serializable { private long id; private byte[] blobby; private String clobby; @Id @GeneratedValue(strategy=GenerationType.AUTO) public long getId() { return id; } public void setId(long id) { this.id = id; } @Lob @Basic(fetch = FetchType.EAGER) public byte[] getBlobby() { return blobby; } public void setBlobby(byte[] blobby) { this.blobby = blobby; } @Lob @Basic(fetch = FetchType.EAGER) public String getClobby() { return clobby; } public void setClobby(String clobby) { this.clobby = clobby; } }
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "blob" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
This tutorial shows you how to cache your entities using EJB 3.0 for JBoss
To avoid roundtrips to the database, you can use a cache for your entities. JBoss EJB3 uses Hibernate as the JPA implementation which has support for a second-level cache. The Hibernate setup used for our JBoss EJB 3.0 implementation uses JBossCache as its underlying cache implementation. With caching enabled:
JBossCache allows you to specify timeouts to cached entities. Entities not accessed within a certain amount of time are dropped from the cache in order to save memory.
Furthermore, JBossCache supports clustering. If running within a cluster, and the cache is updated, changes to the entries in one node will be replicated to the corresponding entries in the other nodes in the cluster.
Take a look at META-INF/persistence.xml
which sets up the caching for this deployment:
<property name="hibernate.cache.use_second_level_cache" value="true"/> <property name="hibernate.cache.use_query_cache" value="true"/> <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.jbc2.JndiMultiplexedJBossCacheRegionFactory"/> <property name="hibernate.cache.region.jbc2.cachefactory" value="java:CacheManager"/> <property name="hibernate.cache.region.jbc2.cfg.entity" value="mvcc-entity"/> <property name="hibernate.cache.region.jbc2.cfg.query" value="local-query"/> <property name="hibernate.show_sql" value="true"/>
hibernate.show_sql
property in the persistence.xml to true.
When this property is set, Hibernate will print to STDOUT the sql queries that are fired to the database, when
the entity is being operated upon. This will, later on, help us in verifying whether the entity is being picked up
from cache or is being loaded from database through a SQL query.
You define your entities org.jboss.tutorial.cachedentity.bean.Customer
and org.jboss.tutorial.cachedentity.bean.Contact
the normal way.
The default behaviour is to not cache anything, even with the settings shown above, in the persistence.xml.
A very simplified rule of thumb is that you will typically want to do caching for objects that rarely change,
and which are frequently read. We also annotate the classes with the @Cache
annotation to
indicate that the entities should be cached.
@Entity @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) public class Customer implements java.io.Serializable { ...
@Entity @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) public class Contact implements Serializable { ...
This defines that the Customer and the Contact entities need to be cached. Any attempt to look up
Customer
or Contact
by their primary key, will first attempt
to read the entry from the cache. If it cannot be found it will try and load it up from the database.
You can also cache relationship collections. Any attempt to access the contacts
collection of
Customer
will attempt to load the data from the cache before hitting the database:
@Entity @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) public class Customer implements java.io.Serializable { ... @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) @OneToMany(mappedBy="customer", fetch=FetchType.EAGER, cascade=CascadeType.ALL) public Set<Contact> getContacts() { return contacts; } ... }
Open org.jboss.tutorial.cachedentity.client.CachedEntityRun
. It takes two arguments, they are the
server:jndiport of the two nodes to use. If you look at the 'run' target of META-INF/build.xml
you will see that they both default to localhost:1099, so in this case node1 and node2 will be the same,
i.e. no clustering takes place.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "cachedentity" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure the "all" server configuration of JBossAS-5.x is running
$ ant $ ant run run: [java] Saving customer to node1 = localhost:1099 [java] Looking for customer on node2 = localhost:1099 (should be available in cache) [java] Found customer on node2 (cache). Customer details follow: [java] Customer: id=1; name=JBoss [java] Contact: id=2; name=Kabir [java] Contact: id=1; name=Bill
$ mvn clean install -PRunSingleTutorial
On the server you will notice these logs:
02:14:04,528 INFO [STDOUT] Hibernate: insert into Customer (id, name) values (null, ?) 02:14:04,529 INFO [STDOUT] Hibernate: call identity() 02:14:04,542 INFO [STDOUT] Hibernate: insert into Contact (id, CUST_ID, name, tlf) values (null, ?, ?, ?) 02:14:04,543 INFO [STDOUT] Hibernate: call identity() 02:14:04,544 INFO [STDOUT] Hibernate: insert into Contact (id, CUST_ID, name, tlf) values (null, ?, ?, ?) 02:14:04,545 INFO [STDOUT] Hibernate: call identity() 02:14:04,545 INFO [EntityTestBean] Created customer named JBoss with 2 contacts 02:14:04,634 INFO [EntityTestBean] Find customer with id = 1 02:14:04,645 INFO [STDOUT] Hibernate: select customer0_.id as id0_1_, customer0_.name as name0_1_, contacts1_.CUST_ID as CUST4_3_, contacts1_.id as id3_, contacts1_.id as id1_0_, contacts1_.CUST_ID as CUST4_1_0_, contacts1_.name as name1_0_, contacts1_.tlf as tlf1_0_ from Customer customer0_ left outer join Contact contacts1_ on customer0_.id=contacts1_.CUST_ID where customer0_.id=? 02:14:04,682 INFO [EntityTestBean] Customer with id = 1 found 02:14:04,756 INFO [EntityTestBean] Find customer with id = 1 02:14:04,801 INFO [EntityTestBean] Customer with id = 1 found
As you can see, the first time the customer with id = 1 was being requested through the entity manager, a database query was fired to fetch it, since it was not available in cache. The Customer object was then loaded from the database and stored in the cache. As can be seen in the next request to fetch the customer with the same id. This request to the entity manager, pulls up the entity from the cache instead of firing a database query.
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.
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.
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.
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.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "callbacks" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
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
$ 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.
The EJB 3.0 specification allows you to define a primary key class as a @Embeddable
and use it as the
primary key of your Entity bean. One or more properties can be used as members of the primary key for that particular table.
This tutorial is an adaptation of the "relationships" tutorial. It adds a primary key class to Customer that holds both
the name
and id
of the Customer.
@Embeddable public class CustomerPK implements java.io.Serializable { private long id; private String name; public CustomerPK() { } public CustomerPK(long id, String name) { this.id = id; this.name = name; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int hashCode() { return (int) id + name.hashCode(); } public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof CustomerPK)) return false; if (obj == null) return false; CustomerPK pk = (CustomerPK) obj; return pk.id == id && pk.name.equals(name); } }
Open up org.jboss.tutorial.composite.bean.Customer
and look for the getPk()
method.
This defines the primary key class.
@EmbeddedId public CustomerPK getPk() { return pk; }
The org.jboss.tutorial.composite.bean.CustomerPK
class is mapped to
org.jboss.tutorial.composite.bean.Customer
just like any other embeddable object.
The additional @EmbeddedId
annotation specifies that it will be the primary key.
If you provide a primary key class, JBoss cannot auto-generate the key for you. You must allocate a CustomerPK class and instantiate it with your id and name when you create the Customer.
There is a many-to-many relationship between org.jboss.tutorial.composite.bean.Customer
and
org.jboss.tutorial.composite.bean.Flight
. In order to have a many-to-many relationship there
needs to be a distinct join table that maps the many-to-many relationship. This is called an association table.
You need to use the @JoinTable
annotation to define this join table. The @JoinTable
must be defined on both sides of the bi-directional relationship. Let's look at the Customer side of the relationship
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER, mappedBy="customers") @JoinTable(name="flight_customer_table", joinColumns={@JoinColumn(name = "FLIGHT_ID")}, inverseJoinColumns={@JoinColumn(name = "CUSTOMER_ID"), @JoinColumn(name = "CUSTOMER_NAME")}) public Set<Flight> getFlights() { return flights; }
The mappedBy
attribute specifies which side of the relationship is responsible for managing the relationship.
If it is not set, then that side is responsible. So, for this example, the Flight
Entity is responsible
for managing the relation. In this example, we are specifying multiple inverseJoinColumns
because Customer has a composite primary key.
Let's look at the other side of the relationship in Flight:
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER) @JoinTable(name = "flight_customer_table", joinColumns = {@JoinColumn(name = "FLIGHT_ID")}, inverseJoinColumns = {@JoinColumn(name = "CUSTOMER_ID"), @JoinColumn(name = "CUSTOMER_NAME")}) public Set<Customer> getCustomers() { return customers; }
The Flight
Entity must also define the @ManyToMany
and @JoinTable
.
The database associate table will look like this:
create table FLIGHT_CUSTOMER_TABLE ( CUSTOMER_ID integer, CUSTOMER_NAME varchar, FLIGHT_ID integer );
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "composite" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Air France customers [java] Bill [java] Monica [java] USAir customers [java] Molly
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
Dependencies of an EJB on a service or services, including other EJBs, may be specified through the
<depends>
tag of the jboss.xml deployment descriptor. The <depends>
tag is analagous to the @org.jboss.ejb3.annotation.Depends
annotation. The dependencies control
the deployment of EJBs such that an EJB will not deploy until all of it's dependencies have successfully deployed.
Take a look at META-INF/jboss-service.xml
. This service deployment descriptor starts
a service based on org.jboss.tutorial.dependency.bean.DependedOn
.
<server> <mbean code="org.jboss.tutorial.dependency.bean.DependedOn" name="jboss.test:service=DependedOn"/> </server>
Take a look at META-INF/jboss.xml
. This deployment descriptor indicates that the
HasXmlMBeanDependencyBean
is dependent on the jboss.test:service=DependedOn
started by jboss-service.xml
. The HasXmlMBeanDependencyBean
will not
deploy until the jboss.test:service=DependedOn
service has successfully started.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "dependency" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Lookup and bean access succeeded
$ mvn clean install -PRunSingleTutorial
EJB 3.0 is backward compatible to EJB 2.x clients and supports the use of local and remote home interfaces as well as initialization methods (e.g. ejbCreate()). This capability is configured through annotations and/or through deployment descriptors.
Take a look at org.jboss.tutorial.ejb21_client_adaptors.bean.Session1Bean
. Note
that the class is annotated with @RemoteHome
and the ejbCreate()
method is annotated
with @Init
. The former annotation indicates that the bean provides a EJB 2.1 style home interface. The latter
annotation indicates that when the create()
method is invoked from the home interface, the bean is initialized
via the ejbCreate
method.
The initialization method (annotated with @Init
) name is not restricted to be ejbCreate(). You can
specify any other name for that method.
org.jboss.tutorial.ejb21_client_adaptors.bean.Session2Bean
illustrates the use of a local home interface.
There's a very important difference between the remote
and a business-remote
interface. The EJB2.x remote interfaces, which extend from EJBObject, are referred through the <remote>
tag in the ejb-jar.xml. On the other hand, the EJB3 style Plain Old Java Interface which is implemented by your EJB3 style
POJO bean is known as the business-remote interface and is represented by the @Remote
and it's
corresponding <business-remote>
tag in ejb-jar.xml.
Similar is the case with <local>
and the <business-local>
tags in ejb-jar.xml.
In this tutorial, you will notice that we are using remote
and local
interfaces and
not business-remote
and business-local
interfaces.
Since we are not using any business-remote
or business-local
interfaces, in this tutorial,
unlike the "jndibinding" tutorial, we cannot use the @RemoteBinding
or @LocalBinding
annotations to bind the EJBs. Instead, we configure the jndi-names for these beans through the META-INF/jboss.xml
:
<session> <ejb-name>Session1</ejb-name> <jndi-name>Session1Remote</jndi-name> </session> <session> <ejb-name>Session2</ejb-name> <local-jndi-name>Session2Local</local-jndi-name> </session>
Similarly, org.jboss.tutorial.ejb21_client_adaptors.bean.DeploymentDescriptorSession1Bean
and
org.jboss.tutorial.ejb21_client_adaptors.DeploymentDescriptorSession2Bean
mimic the behavior of
the first two beans, but use deployment descriptors to indicate the home interface(s) and initialization method(s).
Take a look at the META-INF/ejb-jar.xml
. Note the home
and local-home
tags that indicate the respective home interfaces. Also, note the init-method
tag that indicates the
initialization method(s) executed when beans are created via the home interface(s).
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "ejb21_client_adaptors" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Session1 init value is initialized [java] Session2 init value is initialized [java] DeploymentDescriptor Session1 init value is initialized [java] DeploymentDescriptor Session2 init value is initialized
$ mvn clean install -PRunSingleTutorial
The EJB3 specification allows you to embed plain Java objects within your entities and map the properties of this embedded value object to columns within the entity's table.
The org.jboss.tutorial.embeddable.bean.Customer
entity encapsulates the name of the customer
in the org.jboss.tutorial.embeddable.bean.Name
value object. The
org.jboss.tutorial.embeddable.bean.Name
value object must be tagged with the @Embeddable
annotation.
@Embeddable public class Name implements java.io.Serializable
The properties of Name must then be mapped to columns within Customer's table.
@Embedded @AttributeOverrides({ @AttributeOverride(name = "first", column = {@Column(name = "FIRST_NAME")}), @AttributeOverride(name = "last", column = {@Column(name = "LAST_NAME")}) }) public Name getName() { return name; }
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "embeddable" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
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
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
This tutorial aims at showing how EJBs can be injected in a Servlet. In this tutorial we build an EAR file which contains a EJB module (jar file) and a web module (war file).
Take a look at the META-INF/application.xml
file:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN" "http://java.sun.com/dtd/application_1_3.dtd"> <application> <display-name>jboss-ejb3-tutorial-ejb_injection</display-name> <description>Enterprise application showing injection of EJB in Servlet</description> <module> <web> <web-uri>jboss-ejb3-tutorial-enterprise_webapp.war</web-uri> <context-root>/jboss-ejb3-tutorial-enterprise_webapp</context-root> </web> </module> <module> <ejb>jboss-ejb3-tutorial-enterprise_ejb3app.jar</ejb> </module> </application>
The EJB module in this tutorial consists of a EJB3 SLSB. Take a look at org.jboss.tutorial.enterprise_app_ejb_injection.bean.CalculatorBean
:
@Stateless(name="calculator") @Remote(CalculatorRemote.class) @Local(CalculatorLocal.class) public class CalculatorBean implements CalculatorRemote, CalculatorLocal { ...
We will be using this bean in the servlet for performing the
add
and subtract
operations.
When a EJB packaged in a jar file is deployed as part of an EAR, the default jndi-name for the business-remote interface will be of the form EARName/BeanName/remote. Similarly the local business-remote will have the default jndi-name of the form EARName/BeanName/local.
The web module consists of a index.html
and a servlet org.jboss.tutorial.enterprise_app_ejb_injection.servlet.CalculatorActionServlet
.
Take a look at the WEB-INF/web.xml
which configures the index.html
as the welcome page and
also maps the servlet.
<servlet> <servlet-name>CalculatorActionServlet</servlet-name> <servlet-class>org.jboss.tutorial.enterprise_app_ejb_injection.servlet.CalculatorActionServlet</servlet-class> </servlet> <!-- The servlet and jsp page mappings --> <servlet-mapping> <servlet-name>CalculatorActionServlet</servlet-name> <url-pattern>/CalculatorAction</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>
The org.jboss.tutorial.enterpise_app_ejb_injection.bean.CalculatorLocal
will be injected in the
org.jboss.tutorial.enterprise_app_ejb_injection.servlet.CalculatorActionServlet
using the @EJB
annotation:
private CalculatorLocal calculator; /** * Injecting the EJB */ @EJB(name = "calculator") public void setCalculator(CalculatorLocal calculator) { this.calculator = calculator; }
For the injection to take place in a web module, your web.xml should use the 2.5 version of the web-app xsd:
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "enterprise_app_ejb_injection" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant
This will deploy the ear into the JBossAS-5.x server ("default" configuration).
Add
or subtract
.
$ mvn clean install -PRunSingleTutorial
This will create the EAR in the target
folder of the tutorial. Copy this EAR to the deploy folder of JBossAS-5.x
and start the server (if it's already not started). Then follow the steps mentioned above, to access the servlet from the web browser.
Entity Beans of EJB2.x have been given a complete overhaul in EJB 3.0.
Entity beans (or Entities, as they are now called) are plain Java objects that can be allocated
with new
and attached/detached/reattached to persistence storage.
Entities are not remotable and must be accessed through the new javax.persistence.EntityManager
service.
JBoss's EJB 3.0 entity implementation is built on top of Hibernate.
First let's look at the example implementation of two related Entities.
org.jboss.tutorial.entity.bean.Order
and org.jboss.tutorial.entity.bean.LineItem
.
These two entities form a one-to-many relationship.
The persistence determines the persistent properties by looking for all getter/setter method pairs. By default, all getter/setter methods will be treated as persistence properties.
Defining an Entity is easy. First, annotate the class as an @Entity
. In the minimum, you must at
least define a primary key field using the @Id
annotation.
@Id @GeneratedValue public int getId() { return id; }
Annotations must be defined on the getter method or on the field. However, you cannot have a mix of annotations
on the field and on the methods. The following example where we are mapping the name
through
the field and mapping the age
through the method is not allowed:
@Entity public class Employee implements java.io.Serializable { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; @Column (name="NAME") private String name; private int age; @Column (name="AGE") public int getAge() { return this.age } public String getName() { } // other getter/setters }
The @Id
annotation tells the container that id
is the primary key property.
The @GeneratedValue
tells that it should be automatically generated by the container.
The table name is specified using the @Table
annotation
@Table(name = "PURCHASE_ORDER")
If the table name isn't specified it defaults to the simple name of the class. For instance, the LineItem entity would be mapped to the LINEITEM table.
The Order bean also defines a one to many relationship.
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="order") public Collection<LineItem> getLineItems() { return lineItems; }
Generics must be used so that the container can determine what the entity Order is related to.
CascadeType.ALL
specifies that when an Order is created, any LineItems held in the lineItems
collection will be created as well (CascadeType.PERSIST
). If the Order is deleted from persistence storage,
all related LineItems will be deleted (CascadeType.REMOVE
). If an Order instance is reattached to persistence
storage, any changes to the LineItems collection will be merged with persistence storage (CascadeType.MERGE
).
FetchType.EAGER
specifies that when the Order is loaded whether or not to pre-fetch the relationship as well.
If you want the LineItems to be loaded on demand, then specify FetchType.LAZY
.
The mappedBy
attribute specifies that this is a bi-directional relationship that is managed by the order property
on the LineItem entity.
@ManyToOne @JoinColumn(name = "order_id") public Order getOrder() { return order; }
The @JoinColumn
specifies the foreign key column within the LineItem table.
The org.jboss.tutorial.entity.bean.ShoppingCartBean
allocates and stores all instances
of Orders and LineItems. If you look at the example you can see that ShoppingCart interacts with Order as a plain Java object.
It allocates it with new
. It can pass the Order back to the client. The checkout()
method actually stores it with persistence storage by using the required EntityManager service.
The EntityManager service is injected using field injection and the @PersistenceContext
annotation.
@PersistenceContext private EntityManager manager;
The EntityManager is central to EJB 3.0 as there are no homes. The EntityManager is used to do querying, creating, find by primary key, and removal of entities in EJB3.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "entity" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Buying 2 memory sticks [java] Buying a laptop [java] Print cart: [java] Total: $3000.0 [java] 2 Memory stick 1000.0 [java] 1 Laptop 2000.0 [java] Checkout
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
Usually, an EntityManager in JBoss EJB 3.0 lives and dies within a JTA transaction. Once the transaction is finished, all persistent objects are detached from the EntityManager and are no longer managed. Any local caching the EntityManager instance had done is lost. JBoss EJB 3.0 allows you to define long-living EntityManagers that live beyond the scope of a JTA transaction. This is called an Extended Persistence Context. When you specify that an injected EntityManager is an extended persistence context, all object instances remain managed.
Extended persistence contexts can only be used within Stateful session beans. Take a look at
org.jboss.tutorial.extended.bean.ShoppingCartBean
.
@Stateful @Remote(ShoppingCart.class) public class ShoppingCartBean implements ShoppingCart { @PersistenceContext(type=PersistenceContextType.EXTENDED) EntityManager em; @EJB StatelessLocal stateless; private Customer customer; public long createCustomer() { customer = new Customer(); customer.setName("William"); em.persist(customer); return customer.getId(); } public void update() { customer.setName("Bill"); } ... }
To inject an extended persistence context, you use the type()
attribute and set it to EXTENDED
.
If you look at the createCustomer()
method you notice that it is persisting a Customer
object
and storing a reference to that created object within a member variable of the stateful bean. When the update() method is called,
you see that the customer's state is modified. Since the entity manager used is EXTENDED, the customer member variable remains
managed by the entitymanager and the modified state will be synchronized with the database.
An even more interesting use case is when you combine extended persistence contexts with non-transactional methods. If you interact with an extended persistence context outside of a transaction, the inserts, updates, and deletes will be queued until you access the persistence context within a transaction. This means that any persist(), merge(), or remove() method you call will not actually result in a JDBC execution and thus an update of the database until you manually call EntityManager.flush().
Why is this useful? Consider the usecase of a Setup Wizard on a website. The Wizard has seven steps, seven web pages to enter stuff in. It is extremely unwise to have a JTA transaction that spans multiple http requests, yet you do not want to commit anything to the database until all steps are complete. Your code can interact with the EntityManager as it wants and you do not have to worry about updates or writing a lot of gluecode in your application to do all the entity manager's work in the final step of the wizard. Efficient and highly performant. Because the managed persistent objects remain attached to the persistent context, all optmistic versioning checks can also be maintained within the application transaction. Here's an example on how to do this.
@Stateful @Remote(ShoppingCart.class) public class ShoppingCartBean implements ShoppingCart { @PersistenceContext(type=PersistenceContextType.EXTENDED) EntityManager em; @EJB StatelessLocal stateless; private Customer customer; public long createCustomer() { customer = new Customer(); customer.setName("William"); em.persist(customer); return customer.getId(); } @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void never() { customer.setName("Bob"); } @Remove public void checkout() { } }
Calling the never() method will update the managed customer object reference, but will not result in a database update until checkout() is called. The spec requires that any time you invoke a transactional method of a stateful bean, that the EntityManager join the transaction. Therefore, our never() update will be committed at the end of the checkout() method (which by default has the REQUIRED trasaction attribute).
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "extended_pc" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Created customer: William [java] Customer's name should still be William as pc was not yet flushed: Customer.getName() == William [java] Now that the pc has been flushed name should be 'Bob': Customer.getName() == Bob [java] Created customer: William [java] ShoppingCartBean.customer should stay managed because we're in an extended PC: Customer.getName() == Bill [java] Extended persistence contexts are propagated to nested EJB calls: Customer.getName() == Bill Jr.
$ mvn clean install -PRunSingleTutorial
To facilitate test driven development, the EJB 3.0 specification allows you to use annotations to inject
dependencies through annotations on fields or setter methods. Instead of complicated XML ejb-refs or resource refs,
you can use the @EJB
and @Resource
annotations to set the value of a
field or to call a setter method within your session bean with anything registered within JNDI.
You can use the @EJB
annotation to inject EJB references and @Resource
to access datasources.
Open up org.jboss.tutorial.injection.bean.ShoppingCartBean
.
ShoppingCartBean uses the Calculator stateless session EJB to do calculations. The example shows two ways to get access
to the Calculator EJB. One is:
@EJB private Calculator calculator;
When the ShoppingCartBean instance is created, the EJB container will set the calculator field using the jndiName of that particular referenced EJB.
You are not limited to injecting dependencies on fields. You can also use @EJB on a setter method. The below example from
ShoppingCartBean uses the @EJB
annotation to inject the reference to the Calculator session bean:
private Calculator set; @EJB(beanName="org.jboss.tutorial.injection.bean.CalculatorBean") public void setCalculator(Calculator c) { set = c; }
The @javax.annotation.Resource
annotation allows you to inject resources.
@Resource(mappedName="DefaultDS") private javax.sql.DataSource ds;
In JBoss, whenever the mappedName() attribute is specified (with @Resource, @EJB), JBoss will use that as the GLOBAL jndi name to look it up.
The @Resource annotation is used to inject these singletons as well:
@Resource javax.ejb.SessionContext ctx; @Resource javax.ejb.TimerService timer; @Resource javax.ejb.UserTransaction ut;
@EJB
and @Resource
also create an entry within the JNDI ENC of the bean. So, the above @EJB
injection will create an entry for the reference calculator bean under "java:comp/env/ejb/calculator".
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "injection" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Buying 1 memory stick [java] Buying another memory stick [java] Buying a laptop [java] Print cart: [java] 2 Memory stick [java] 1 Laptop [java] Checkout
$ mvn clean install -PRunSingleTutorial
The EJB 3.0 spec defines the ability to apply custom made interceptors
to the business methods
of your session and message driven beans. EJB
3.0 interceptors take the form of methods annotated with
the
@javax.ejb.AroundInvoke
annotation. These methods must have the following signature:
@javax.ejb.AroundInvoke public Object <Arbitrary method name>(javax.ejb.InvocationContext ctx) throws java.lang.Exception
You can apply interceptors to JBoss specific @Service and @Consumer beans
You can either define an interceptor method in the bean class itself, or in separate classes. There can only be one interceptor method per class.
Take a look at
org.jboss.tutorial.interceptor.bean.EmailMDB
. It contains this method:
@AroundInvoke public Object mdbInterceptor(InvocationContext ctx) throws Exception { System.out.println("*** Intercepting call to EmailMDB.mdbInterceptor()"); return ctx.proceed(); }
This method will wrap the call to EmailMDB.onMessage(). The call to ctx.proceed() causes the next object in the chain of interceptors to get invoked. At the end of the chain of interceptors, the actual bean method gets called.
Similarly
org.jboss.tutorial.interceptor.bean.EmailSystemBean
has a method annotated with
@AroundInvoke
@AroundInvoke public Object myBeanInterceptor(InvocationContext ctx) throws Exception { if (ctx.getMethod().getName().equals("emailLostPassword")) { System.out.println("*** EmailSystemBean.myBeanInterceptor - username: " + ctx.getParameters()[0]); } return ctx.proceed(); }
The rest of this example will be looking at adding interceptors
external to the bean class, more specifically to
EmailSystemBean
. Interceptors can be bound in three different ways:
Default
Class level
Method level
An external interceptor instance follows the lifecycle of the target bean instance.
We will see how class and method-level interceptors can be bound to a bean or a bean's method using annotations or xml. Default interceptors can only be bound via xml. A Default interceptor is an interceptor that is invoked whenever a business method is invoked on any bean within the deployment.
org.jboss.tutorial.interceptor.bean.DefaultInterceptor
defines an
@AroundInvoke
method with the required method signature.
@AroundInvoke public Object intercept(InvocationContext ctx) throws Exception { System.out.println("*** DefaultInterceptor intercepting " + ctx.getMethod().getName()); try { return ctx.proceed(); } finally { System.out.println("*** DefaultInterceptor exiting"); } }
As mentioned, default interceptors can only be applied via xml. In
META-INF/ejb-jar.xml
we have the following:
<assembly-descriptor> <!-- Default interceptor that will apply to all methods for all beans in deployment --> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>org.jboss.tutorial.interceptor.bean.DefaultInterceptor</interceptor-class> </interceptor-binding> ... </assembly-descriptor>
Using *
for the ejb-name says that DefaultInterceptor
is a default interceptor,
i.e. it should intercept all calls to all beans within the deployment unit. We could have added more
interceptor-class
entries to bind more interceptors, as shown here:
<assembly-descriptor> <!-- Default interceptor that will apply to all methods for all beans in deployment --> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>org.jboss.tutorial.interceptor.bean.DefaultInterceptor</interceptor-class> <interceptor-class>org.jboss.tutorial.interceptor.bean.AnotherInterceptor</interceptor-class> </interceptor-binding> ... </assembly-descriptor>
In this case DefaultInterceptor
would be invoked before AnotherInterceptor
.
Class-level interceptors can be bound using xml or annotations.
org.jbos.tutorial.interceptor.EmailSystemBean
has been annotated:
@Stateless @Interceptors ({TracingInterceptor.class}) public class EmailSystemBean { ... }
This says that the @AroundInvoke
annotated method of org.jboss.tutorial.interceptor.bean.TracingInterceptor
should wrap all calls
to EmailSystemBean
's business methods. The @Interceptors
annotation can take an
array of classes, so you can bind more than one class-level interceptor this way, e.g.
@Stateless @Interceptors ({TracingInterceptor.class, SomeInterceptor.class}) public class EmailSystemBean { ... }
In the META-INF/ejb-jar.xml
we have the following:
<assembly-descriptor> ... <!-- Class interceptor that will apply to all methods for EmailSystemBean --> <interceptor-binding> <ejb-name>EmailSystemBean</ejb-name> <interceptor-class>org.jboss.tutorial.interceptor.bean.OtherInterceptor</interceptor-class> </interceptor-binding> ... </assembly-descriptor>
Note that here we are specifying the ejb-name of the bean we want to apply the interceptor to.
Hence, the @AroundInvoke
annotated method of org.jboss.tutorial.interceptor.bean.OtherInterceptor
will wrap all calls to EmailSystemBean
's business methods.
Just as we can define default and class-level interceptors, we can also bind an interceptor to a particular business method of a bean
org.jboss.tutorial.interceptor.bean.EmailSystemBean
's sendBookingConfirmationMessage()
method has been annotated with the @Interceptors annotation specifying interceptors that will intercept when this perticular
method is invoked.
@Interceptors({AccountsConfirmInterceptor.class}) public void sendBookingConfirmationMessage(long orderId) { ... }
Method-level interceptors are in addition to default and class-level interceptors, meaning that when we call
EmailSystemBean.sendBookingConfirmationMessage()
we get intercepted by
DefaultInterceptor (default)
TracingInterceptor (class-level)
OtherInterceptor (class-level)
AccountsConfirmInterceptor (method-level)
An interceptor method can choose to abort an invocation, by not calling
InvocationContext.proceed()
as is done in AccountConfirmInterceptor
's interceptor method
when a confirmation already exists.
We can also bind interceptors to a single business method within a bean using xml.
But first let's define an interceptor using xml. All the examples we have looked at so far have used the @AroundInvoke
annotation to mark the interceptor methods. In META-INF/ejb-jar.xml
we have the following:
<interceptors> <interceptor> <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class> <around-invoke> <method-name>sendCancelMessage</method-name> </around-invoke> ... </interceptor> </interceptors>
This tells us that the sendCancelMessage()
method of org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
is the interceptor method of this interceptor class. To bind interceptors to a particular method using xml, is much the same as how
this was done for class-level interceptors apart from that we specify the method-name
of the method we want to intercept.
From our META-INF/ejb-jar.xml
:
<assembly-descriptor> ... <!-- Method interceptor will apply to sendBookingCancellationMessage for EmailSystemBean --> <interceptor-binding> <ejb-name>EmailSystemBean</ejb-name> <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class> <method> <method-name>sendBookingCancellationMessage</method-name> </method> </interceptor-binding> ... </assembly-descriptor>
In the case of overloaded methods, we can further narrow down the method by specifying the method parameters, as in this example:
<assembly-descriptor> ... <!-- Method interceptor will apply to sendBookingCancellationMessage for EmailSystemBean --> <interceptor-binding> <ejb-name>SomeBean</ejb-name> <interceptor-class>SomeInterceptor</interceptor-class> <method> <method-name>methodWithParam</method-name> <method-params> <method-param>int</method-param> <method-param>java.lang.String[]</method-param> </method-params> </method> </interceptor-binding> </assembly-descriptor>
SomeInterceptor
will only get applied to the method of SomeBean
that matches the signature,
i.e. methodWithParam(int, java.lang.String[])
For some beans we may want to exclude the default interceptors configured for the deployment, and for some methods within a bean class we may want to exclude the class interceptors for the bean (and/or the default interceptors)
The @javax.ejb.ExcludeDefaultInterceptors
annotation can be applied to a bean class or a method.
If applied to a bean class, default interceptors will not get invoked for any of that bean class's methods. If applied
to a bean business method, default interceptors will not get invoked when calling that method.
The @javax.ejb.ExcludeClassInterceptors
annotation can be applied to a bean method. When this annotation
is used class-level interceptors will not get invoked when calling that method.
org.jboss.tutorial.interceptor.bean.EmailMDB
defines
@ExcludeDefaultInterceptors public class EmailMDB implements MessageListener { ... }
so DefaultInterceptor
is not invoked when invoking the EmailMDB.onMessage()
org.jboss.tutorial.interceptor.bean.EmailSystemBean
's noop
method is annotated with both
@ExcludeClassInterceptors
and @ExcludeDefaultInterceptors
and so will not get intercepted
by any of these.
@ExcludeClassInterceptors @ExcludeDefaultInterceptors public void noop() { System.out.println("<In EmailSystemBean.noop business method"); System.out.println("Exiting EmailSystemBean.noop business method>"); }
We can also exclude class and method-level interceptors within an interceptor-binding
by specifying the
exclude-class-interceptors
and exclude-default-interceptors
elements.
<assembly-descriptor> ... <interceptor-binding> <ejb-name>EmailSystemBean</ejb-name> <exclude-default-interceptors>true</exclude-default-interceptors> <exclude-class-interceptors>true</exclude-class-interceptors> <method> <method-name>noop2</method-name> </method> </interceptor-binding> ... </assembly-descriptor>
If an interceptor (or bean class) inherits from another class which has a method declared to be an interceptor method, the interceptor methods from the super class will be invoked first. However, if the interceptor method in the superclass has been overridden (regardless of if the overriding method is declared to be an interceptor method or not) the superclass method is not invoked.
Both org.jboss.tutorial.interceptor.bean.AccountsConfirmInterceptor
and
org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor
inherit from
org.jboss.tutorial.interceptor.bean.AccountsInterceptor
.
AccountsInterceptor declares the following interceptor method:
@AroundInvoke public Object intercept(InvocationContext ctx) throws Exception { System.out.println("*** AccountsInterceptor intercepting " + ctx.getMethod().getName()); try { return ctx.proceed(); } finally { System.out.println("*** AccountsInterceptor exiting"); } }
This method is overridden by AccountsConfirmInterceptor
, even though it is not AccountsConfirmInterceptor
's
interceptor method:
public class AccountsConfirmInterceptor extends AccountsInterceptor { ... public Object intercept(InvocationContext ctx) throws Exception { //overrides AccountsInterceptor.intercept() so that will not be invoked return null; } @AroundInvoke public Object sendConfirmMessage(InvocationContext ctx) throws Exception { ... } }
So when invoking AccountsConfirmInterceptor
, we simply invoke its sendConfirmMessage()
method.
AccountsCancelInterceptor
, on the other hand, does not override AccountsInterceptor.intercept()
,
so when invoking AccountsCancelInterceptor
, we first get intercepted by AccountsInterceptor.intercept()
followed by AccountsCancelInterceptor.sendCancelMessage()
.
Just like a bean class, an interceptor can be the target of dependency injection (see the "injection" tutorial for details about injection). The format for how this works is the same, and the injection works off the same ENC as the bean to which the interceptor is bound.
org.jboss.tutorial.interceptor.bean.AccountsConfirmInterceptor
has dependency injection set up using annotations:
public class AccountsConfirmInterceptor extends AccountsInterceptor { @Resource(mappedName="java:ConnectionFactory") QueueConnectionFactory cf; @Resource(mappedName="queue/tutorial/accounts") Queue queue; @PersistenceContext EntityManager em; ...
Remember that interceptors follow the same lifecycle as the bean they are bound to. The interceptors are created at the same time as the bean instance is created, and dependecy injection occurs before the first business method is called. In the example just shown, the container:
Looks up java:ConnectionFactory
, binds it in the ENC and injects this into the field cf
Looks up queue/tutorial/accounts
, binds it in the ENC and injects this into the field queue
Obtains the default EntityManager and injects this into the field em
Injection, into interceptors, can also be configured via xml, using the traditional ejb-ref
, ejb-local-ref
,
resource-ref
, resource-env-ref
etc. which bind the global names. The only difference
from beans is that we put this into the {{interceptors}} element defining the interceptor.
<interceptors> <interceptor> <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class> <around-invoke> <method-name>sendCancelMessage</method-name> </around-invoke> <resource-ref> <res-ref-name>jms/ConnFactory</res-ref-name> <res-type>javax.jms.QueueConnectionFactory</res-type> <res-auth>Container</res-auth> <mapped-name>java:/ConnectionFactory</mapped-name> <injection-target> <injection-target-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</injection-target-class> <injection-target-name>cf</injection-target-name> </injection-target> </resource-ref> <resource-env-ref> <resource-env-ref-name>accountsQueue</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> <mapped-name>queue/tutorial/accounts</mapped-name> <injection-target> <injection-target-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</injection-target-class> <injection-target-name>queue</injection-target-name> </injection-target> </resource-env-ref> </interceptor> </interceptors>
The container looks up java:/ConnectionFactory
in JNDI and creates the java:comp/env/jms/ConnFactory
entry in the bean's ENC, and following bean creation it injects the connection factory into AccountsCancelInterceptor
's
cf
field.
The container looks up queue/tutorial/accounts
in JNDI and creates the java:comp/env/accountsQueue
entry in the bean's ENC, and following bean creation it injects the queue into AccountsCancelInterceptor
's
queue
field.
By default the ordering of interceptors when invoking a method are
External interceptors
Default interceptors, if present
Class interceptors, if present
Method interceptors, if present
Interceptor method on the bean class (using @AroundInvoke)
Within each group (default, class, method) the order of the interceptors are from left to right as defined in the @Interceptors annotation, and then the xml interceptors.
You can override the default sort order of the external interceptors by specifiying an interceptor-binding
with an interceptor-order
specifying the order of the interceptors
<assembly-descriptor> <interceptor-binding> <ejb-name>EmailSystemBean</ejb-name> <interceptor-order> <interceptor-class>org.jboss.tutorial.interceptor.bean.AccountsCancelInterceptor</interceptor-class> <interceptor-class>org.jboss.tutorial.interceptor.bean.DefaultInterceptor</interceptor-class> <interceptor-class>org.jboss.tutorial.interceptor.bean.OtherInterceptor</interceptor-class> <interceptor-class>org.jboss.tutorial.interceptor.bean.TracingInterceptor</interceptor-class> </interceptor-order> <method> <method-name>sendBookingCancellationMessage</method-name> </method> </interceptor-binding> ... </assembly-descriptor>
So when invoking
EmailSystemBean.sendBookingCancellationMessage
we will get the following interception order,
quite different from the default sort order
AccountsCancelInterceptor (method-level)
DefaultInterceptor (default interceptor)
OtherInterceptor (2nd class-level interceptor)
TracingInterceptor (1st class-level interceptor)
As you have seen the @AroundInvoke
annotated interceptor methods all take a parameter of type
javax.ejb.InvocationContext
. The definition of InvocationContext is:
package javax.interceptor; public interface InvocationContext { public java.lang.Object getTarget(); public java.lang.reflect.Method getMethod(); public java.lang.Object[] getParameters(); public void setParameters(java.lang.Object[] arg0); public java.util.Map getContextData(); public java.lang.Object proceed() throws java.lang.Exception; }
getBean()
- returns the bean instance on which we are calling a method
getMethod()
- returns the method we are calling on the bean instance
getParameters()
- returns the parameters for the method call
setParameters()
- allows you to modify the parameters for the method call
getContextData
- The EJB interceptions are stateless, but the same InvocationContext instance
is used for each interceptor method in a chain. If you want to pass values between interceptor methods in the
business method invocation you can store them in the Map retured by getContextData().
proceed
- As discussed, invokes the next interceptor, or if we are in the last interceptor
invokes the target bean method.
In some case we might want access to the EJBContext, this can be injected into the interceptor instance using the
@Resource
annotation. The AroundInvoke methods share the JNDI name space of the bean for whose
methods they are invoked and execute within the same transaction and security context as the business methods for
which they are invoked.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "interceptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Starting [java] [java] Calling emailLostPassword [java] Waiting 2 seconds [java] [java] Calling sendBookingConfirmationMessage [java] Waiting 2 seconds [java] [java] Calling sendBookingConfirmationMessage [java] Waiting 2 seconds [java] [java] Calling sendBookingCancellationMessage [java] Waiting 2 seconds [java] [java] Calling noop [java] Waiting 2 seconds [java] [java] Calling noop2 [java] Waiting 2 seconds [java] Done
$ mvn clean install -PRunSingleTutorial
21:49:01,888 INFO [STDOUT] *** DefaultInterceptor intercepting emailLostPassword 21:49:01,889 INFO [STDOUT] *** TracingInterceptor intercepting emailLostPassword 21:49:01,889 INFO [STDOUT] *** OtherInterceptor intercepting emailLostPassword 21:49:01,889 INFO [STDOUT] *** EmailSystemBean.myBeanInterceptor - username: whatever 21:49:01,889 INFO [STDOUT] <In EmailSystemBean.emailLostPassword business method 21:49:02,083 INFO [STDOUT] Message sent successfully to remote queue. 21:49:02,130 INFO [STDOUT] Exiting EmailSystemBean.emailLostPassword business method> 21:49:02,130 INFO [STDOUT] *** OtherInterceptor exiting 21:49:02,130 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.emailLostPassword() took 241ms 21:49:02,130 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:02,179 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:02,180 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:02,185 INFO [STDOUT] *** EmailMDB.mdbInterceptor intercepting 21:49:02,185 INFO [STDOUT] ---------------- EMailMDB - Got message, sending email ---------------- 21:49:04,251 INFO [STDOUT] *** DefaultInterceptor intercepting sendBookingConfirmationMessage 21:49:04,251 INFO [STDOUT] *** TracingInterceptor intercepting sendBookingConfirmationMessage 21:49:04,251 INFO [STDOUT] *** OtherInterceptor intercepting sendBookingConfirmationMessage 21:49:04,251 INFO [STDOUT] *** AccountsConfirmInterceptor intercepting 21:49:04,395 INFO [STDOUT] *** AccountsConfirmInterceptor - recording confirmation 21:49:04,431 INFO [STDOUT] *** AccountsConfirmInterceptor - notifying accounts dept sendBookingConfirmationMessage 21:49:04,440 INFO [STDOUT] <In EmailSystemBean.sendBookingConfirmationMessage business method 21:49:04,455 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:04,455 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:04,458 INFO [STDOUT] *** DefaultInterceptor intercepting onMessage 21:49:04,459 INFO [STDOUT] ---------------- AccountsMDB - Got message Confirming order 100 ---------------- 21:49:04,459 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:04,466 INFO [STDOUT] Message sent successfully to remote queue. 21:49:04,467 INFO [STDOUT] Exiting EmailSystemBean.sendBookingConfirmationMessage business method> 21:49:04,467 INFO [STDOUT] *** AccountsConfirmInterceptor exiting 21:49:04,467 INFO [STDOUT] *** OtherInterceptor exiting 21:49:04,467 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingConfirmationMessage() took 216ms 21:49:04,467 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:04,478 INFO [STDOUT] *** EmailMDB.mdbInterceptor intercepting 21:49:04,478 INFO [STDOUT] ---------------- EMailMDB - Got message, sending email ---------------- 21:49:06,533 INFO [STDOUT] *** DefaultInterceptor intercepting sendBookingConfirmationMessage 21:49:06,533 INFO [STDOUT] *** TracingInterceptor intercepting sendBookingConfirmationMessage 21:49:06,533 INFO [STDOUT] *** OtherInterceptor intercepting sendBookingConfirmationMessage 21:49:06,533 INFO [STDOUT] *** AccountsConfirmInterceptor intercepting 21:49:06,547 INFO [STDOUT] *** AccountsConfirmInterceptor - order has already been confirmed aborting 21:49:06,548 INFO [STDOUT] *** AccountsConfirmInterceptor exiting 21:49:06,548 INFO [STDOUT] *** OtherInterceptor exiting 21:49:06,548 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingConfirmationMessage() took 15ms 21:49:06,548 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:08,577 INFO [STDOUT] *** AccountsInterceptor intercepting sendBookingCancellationMessage 21:49:08,577 INFO [STDOUT] *** AccountsCancelInterceptor intercepting sendBookingCancellationMessage 21:49:08,577 INFO [STDOUT] *** AccountsConfirmInterceptor - notifying accounts dept 21:49:08,593 INFO [STDOUT] *** DefaultInterceptor intercepting sendBookingCancellationMessage 21:49:08,593 INFO [STDOUT] *** OtherInterceptor intercepting sendBookingCancellationMessage 21:49:08,593 INFO [STDOUT] *** TracingInterceptor intercepting sendBookingCancellationMessage 21:49:08,593 INFO [STDOUT] <In EmailSystemBean.sendBookingCancellationMessage business method 21:49:08,605 INFO [STDOUT] Message sent successfully to remote queue. 21:49:08,606 INFO [STDOUT] Exiting EmailSystemBean.sendBookingCancellationMessage business method> 21:49:08,606 INFO [STDOUT] *** TracingInterceptor invocation of org.jboss.tutorial.interceptor.bean.EmailSystemBean.sendBookingCancellationMessage() took 13ms 21:49:08,606 INFO [STDOUT] *** OtherInterceptor exiting 21:49:08,606 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:08,606 INFO [STDOUT] *** AccountsCancelInterceptor exiting 21:49:08,606 INFO [STDOUT] *** AccountsInterceptor exiting 21:49:08,617 INFO [STDOUT] *** EmailMDB.mdbInterceptor intercepting 21:49:08,617 INFO [STDOUT] ---------------- EMailMDB - Got message, sending email ---------------- 21:49:08,620 INFO [STDOUT] *** DefaultInterceptor intercepting onMessage 21:49:08,620 INFO [STDOUT] ---------------- AccountsMDB - Got message Cancelling order 100 ---------------- 21:49:08,620 INFO [STDOUT] *** DefaultInterceptor exiting 21:49:10,628 INFO [STDOUT] <In EmailSystemBean.noop business method 21:49:10,628 INFO [STDOUT] Exiting EmailSystemBean.noop business method> 21:49:12,648 INFO [STDOUT] <In EmailSystemBean.noop2 business method 21:49:12,648 INFO [STDOUT] Exiting EmailSystemBean.noop2 business method>
Look at the JBoss console window to see the output of the interceptions taking place, the EmailMDB and AccountsMDB might occur in slightly different places since they execute asynchronously.
You can ignore the [WARN] messages:
21:49:02,179 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container 21:49:02,180 WARN [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
The EJB 3.0 specification supports the usage of deployment descriptors to describe ejb metadata
or override most metadata described via annotations. The jboss.xml deployment descriptor describes
JBoss specific metadata, including remote and local JNDI names, cluster configuration, a security domain,
additional references, security principal, and container configuration. Annotations which define the ejb
(e.g. @Stateful
, @Remote
) cannot be overridden.
Take a look at META-INF/jboss.xml
.
jndi-name
element defines the remote JNDI binding for the ShoppingCartBean
stateful session bean.
This JNDI binding overrides the default binding or a binding specified through the @RemoteBinding
annotation.
Similarly, the local-jndi-name
element specifies the local JNDI binding.
clustered
element indicates that the ejb is clustered, with partition-name
and
load-balance-policy
specifying the cluster name and load balance policy, respectively.
These elements will override the parameters specified in the @Clustered
annotation.
In this example, you will see that the ShoppingCartBean
ejb is clustered.
security-domain
element specifies a security domain for the ejb, overriding
any security domain set through the @SecurityDomain
annotation. In this example,
you will see that a security domain is set through the jboss.xml deployment descriptor and unless a
Principal and Credential (i.e. user/password) is set in the client, requests to the ejb will fail
with a javax.ejb.EJBAccessException.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "jboss_deployment_descriptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure the "all" server configuration of JBossAS-5.x is running
$ ant $ ant run run: [java] Attempting to buy 1 memory stick with incorrect password [java] Caught javax.ejb.EJBAccessException as expected [java] Setting user/password [java] bill is a shopper, so is allowed to buy [java] Buying 1 memory stick [java] Buying another memory stick [java] Buying a laptop [java] Print cart: [java] 2 Memory stick [java] 1 Laptop [java] bill is not a clerk, so is not allowed to price check [java] Caught EJBAccessException as expected [java] Checkout [java] Should throw an object not found exception by invoking on cart after @Remove method [java] Successfully caught no such object exception. [java] Caught javax.ejb.EJBAccessException, for SLSB, as expected [java] Now passing the correct user/password to access the SLSB [java] Successfully accessed SLSB
$ mvn clean install -PRunSingleTutorial
JBossAS comes bundled with Quartz integration packaged as a JCA Message-Inflow resource adapter quartz-ra.rar
.
This integration allows you to schedule stateless or stateful quartz jobs and have the job be posted to a
Message Driven bean. The Quartz Resource Adapter creates a non-persistent scheduler. Jobs are created from
the information in the MDB's activation config spec.
Currently only cron jobs are allowed to be configured.
Take a look at the org.jboss.tutorial.jca_inflow_quartz.bean.AnnotatedQuartzMDBBean
:
@MessageDriven(activationConfig = {@ActivationConfigProperty(propertyName = "cronTrigger", propertyValue = "0/2 * * * * ?")}) @ResourceAdapter("quartz-ra.rar") public class AnnotatedQuartzMDBBean implements Job { ...
This is a simple MDB that implements the org.quartz.Job
interface. The cronTrigger
activation spec attribute is required. In this you specify a cron syntax as documented in the Quartz documentation.
The @org.jboss.ejb3.annotation.ResourceAdapter
annotation is used to tell the EJB container which
resource adapter to use for the inflow implementation.
Instead of using annotations, you can also use a deployment descriptor to configure the MDB for Quartz
integration. Take a look at META-INF/ejb-jar.xml
and META-INF/jboss.xml
The ejb-jar.xml configures the cronTrigger
through the <activation-config>
element:
<message-driven> <ejb-name>ExampleMDB</ejb-name> <ejb-class>org.jboss.tutorial.quartz.bean.QuartzMDBBean</ejb-class> <transaction-type>Container</transaction-type> <activation-config> <activation-config-property> <activation-config-property-name>cronTrigger</activation-config-property-name> <activation-config-property-value>0/2 * * * * ?</activation-config-property-value> </activation-config-property> </activation-config> </message-driven>
The jboss.xml configures the resource-adapter-name
:
<message-driven> <ejb-name>ExampleMDB</ejb-name> <resource-adapter-name>quartz-ra.rar</resource-adapter-name> </message-driven>
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "jca_inflow_quartz" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure the "default" server configuration of JBossAS-5.x is running
$ ant
This will deploy the jar into the JBossAS-5.x server ("default" configuration).
$ mvn clean install -PRunSingleTutorial
This will create the jar in the target
folder of the tutorial. Copy this jar to the deploy folder of JBossAS-5.x
On the server side when the application is deployed, you will notice these logs:
18:46:57,053 INFO [QuartzMDBBean] ************** JOB: job.4.1232457416127 18:46:58,023 INFO [AnnotatedQuartzMDBBean] ************** here in annotated!!!! 18:46:58,026 INFO [QuartzMDBBean] ************** JOB: job.2.1232457416077 18:47:00,019 INFO [AnnotatedQuartzMDBBean] ************** here in annotated!!!! 18:47:00,021 INFO [QuartzMDBBean] ************** JOB: job.2.1232457416077 18:47:00,023 INFO [QuartzMDBBean] ************** JOB: job.4.1232457416127 18:47:02,020 INFO [AnnotatedQuartzMDBBean] ************** here in annotated!!!! 18:47:02,023 INFO [QuartzMDBBean] ************** JOB: job.2.1232457416077 18:47:03,019 INFO [QuartzMDBBean] ************** JOB: job.4.1232457416127 18:47:04,022 INFO [AnnotatedQuartzMDBBean] ************** here in annotated!!!! 18:47:04,024 INFO [QuartzMDBBean] ************** JOB: job.2.1232457416077 18:47:06,019 INFO [AnnotatedQuartzMDBBean] ************** here in annotated!!!! 18:47:06,021 INFO [QuartzMDBBean] ************** JOB: job.2.1232457416077 18:47:06,024 INFO [QuartzMDBBean] ************** JOB: job.4.1232457416127 18:47:08,019 INFO [AnnotatedQuartzMDBBean] ************** here in annotated!!!! 18:47:08,021 INFO [QuartzMDBBean] ************** JOB: job.2.1232457416077 18:47:09,020 INFO [QuartzMDBBean] ************** JOB: job.4.1232457416127
By default, when the application is deployed in a jar, session beans will bind to JNDI in the form ejbName/remote
for remote
interfaces and ejbName/local
in the case of local interfaces. When the EJBs are deployed in an .ear file, the default
jndi binding will be prepended by the name of the .ear file. So if the ear file name is foo.ear
the default jndi
names would be foo/EJB-NAME/remote
and foo/EJB-NAME/local
. You can override this behavior
by defining your own @org.jboss.ejb3.annotation.LocalBinding
and/or @org.jboss.ejb3.annotation.RemoteBinding
.
To change the JNDI name for your local interface use the @org.jboss.ejb3.annotation.LocalBinding
annotation.
@Stateless @LocalBinding(jndiBinding="custom/MySession") public class MySessionBean implements MySession { }
To change the JNDI name for your remote interface use the @org.jboss.ejb3.annotation.RemoteBinding
annotation.
@Stateless @RemoteBinding(jndiBinding="custom/remote/MySession") public class MySessionBean implements MySession { }
Persistence units are not bound into JNDI by default. You can bind them by defining some jboss specific properties in persistence.xml.
<persistence> <persistence-unit name="manager1"> <jta-data-source>java:/DefaultDS</jta-data-source> <jar-file>MyApp.jar</jar-file> <class>org.acme.Employee</class> <class>org.acme.Person</class> <class>org.acme.Address</class> <properties> <property name="jboss.entity.manager.jndi.name" value="java:/Manager1"/> <property name="jboss.entity.manager.factory.jndi.name" value="java:/Manager1Factory"/> </properties> </persistence-unit> </persistence>
Open up org.jboss.tutorial.jndibinding.client.Client
.
You'll see that it looks up the stateless bean under "Calculator". Also notice that there is no Home interface
and you can begin executing on the stateless bean right away.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "jndibinding" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] 1 + 1 = 2 [java] 1 - 1 = 0
$ mvn clean install -PRunSingleTutorial
The EJB3 specification allows you to define entities that inherit from one another. The inheritance relationships can be reflected in queries as well. So, if you queried based on the base class, the query is polymorphic.
The tutorial example uses the join table strategy to map an inheritance relationship of
org.jboss.tutorial.joininheritance.bean.Pet
, which is the base class
for org.jboss.tutorial.joininheritance.bean.Cat
and
org.jboss.tutorial.joininheritance.bean.Dog
.
With the join table strategy there is a table per class in the hierarchy, but the subclass tables only have the extra attribute they define in their subclass. A discriminator column is NOT required to differentiate between which class type is persisted in a particular row unlike the single table mapping. The persistence manager does not need a discrimiator column to figure out the type.
This is what the annotations look like for Pet:
@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Pet implements java.io.Serializable { ...
@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Dog extends Pet { ...
org.jboss.tutorial.joininheritance.bean.PetDAOBean
stateless EJB wraps
some polymorphic queries.
public List findByWeight(double weight) { return manager.createQuery("from Pet p where p.weight < :weight").setParameter("weight", weight).getResultList(); }
Even though the findByWeight
method queries on Pet, either Dog or Cat instances
can be returned.
The table mapping for this example looks like this:
create table PET ( ID integer primary key, ANIMAL_TYPE varchar, NAME varchar, WEIGHT double ); create table CAT ( ID integer primary key, LIVES int ); create table DOG ( ID integer primary key, NUMBONES int );
The join inheritance mapping is less efficient than the single table strategy as the SQL query is more complicated.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "joininheritance" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Sox [java] Junior
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
This example shows you how to implement an MDB with EJB 3.0 using annotations.
Take a look at org.jboss.tutorial.mdb.bean.ExampleMDB
. The @MessageDriven annotation
defines the bean as an MDB. The activationConfig
attribute contains much of the MDB configuration via
@ActivationConfigProperty
. Also notice that the MDB source contains properties for
destinationType
and destination
The following is the list of standard Activation Config Properties available from the JCA 1.5 specification. Also listed are the respective types and default values where defined.
Name | Type | Remarks | Mandatory? | Default value |
---|---|---|---|---|
destination | java.lang.String | The jndi name of the Queue or Topic | Yes | |
destinationType | java.lang.String | The type of destination valid values are javax.jms.Queue or javax.jms.Topic | No | |
messageSelector | java.lang.String | The message selector of the subscription | No | |
acknowledgeMode | int | The type of acknowledgement when not using transacted jms - valid values AUTO_ACKNOWLEDGE or DUPS_OK_ACKNOWLEDGE | No | AUTO_ACKNOWLEDGE |
clientID | java.lang.String | The client id of the connection | No | |
subscriptionDurability | String | Whether topic subscriptions are durable. Valid values are Durable or NonDurable | No | NonDurable |
subscriptionName | String | The subsription name of the topic subscription | No |
The following is the list of Activation Config Properties available as JBoss extensions.
Name | Type | Remarks | Mandatory? | Default value |
---|---|---|---|---|
isTopic | boolean | Sets the destinationType | No | false |
providerAdapterJNDI | java.lang.String | The jndi name of the jms provider | No | java:/DefaultJMSProvider |
user | java.lang.String | The user id used to connect to the jms server | No | |
pass | java.lang.String | The password of the user | No | |
maxMessages | int | Read this number of messages before delivering messages to the mdb. Each message is delivered individually on the same thread in an attempt to avoid context excessive context switching | No | 1 |
minSession | int | The minimum number of jms sessions that are available to concurrently deliver messages to this mdb | No | 1 |
maxSession | int | The maximum number of jms sessions that are available to concurrently deliver messages to this mdb | No | 15 |
reconnectInterval | long | The length of time in seconds between attempts to (re-)connect to the jms provider | No | 10 seconds |
keepAlive | long | The length of time in milliseconds that sessions over the minimum are kept alive | No | 60 seconds |
sessionTransacted | boolean | Whether the sessions are transacted | No | true |
useDLQ | boolean | Whether to use a DLQ handler | No | true |
dLQJNDIName | java.lang.String | The JNDI name of the DLQ | No | queue/DLQ |
dLQHandler | java.lang.String | The org.jboss.resource.adapter.jms.inflow.DLQHandler implementation class name | No | org.jboss.resource.adapter.jms.inflow.dlq.GenericDLQHandler |
dLQUser | java.lang.String | The user id used to make the dlq connection to the jms server | No | |
dLQPassword | java.lang.String | The password of the dLQUser | No | |
dLQClientID | java.lang.String | The client id of the dlq connection | No | |
dLQMaxResent | int | The maximum number of times a message is redelivered before it is sent to the DLQ | No | 5 |
redeliverUnspecified | boolean | Whether to attempt to redeliver a message in an unspecified transaction context | No | true |
transactionTimeout | int | Time in seconds for the transaction timeout | No | Default is the timeout set for the resource manager |
DeliveryActive | boolean | Whether the MDB should make the subscription at initial deployment or wait for start() or stopDelivery() on the corresponding MBean. You can set this to false if you want to prevent messages from being delivered to the MDB (which is still starting) during server startup | No | true |
The queue-example-service.xml
file defines the queues for this tutorial.
You can configure MDBs to have default properties using the @org.jboss.ejb3.annotation.DefaultActivationSpecs
annotations. Take a look at custom-ejb3-interceptors-aop.xml
. Here we define a custom container
configuration domain, "Custom Message Driven Bean"
, that adds a @DefaultActivationSpecs
annotation and destinationType
and destination
properties to each MDB using this domain.
Now take a look at org.jboss.tutorial.mdb.bean.DefaultedExampleMDB
. The MDB is configured to use the
"Custom Message Driven Bean"
container configuration domain via the @AspectDomain
annotation.
Note there are no properties defined in the @MessageDriven
annotation (they are all from the defaults).
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "mdb" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Message sent successfully to remote queue queue/tutorial/example [java] Message sent successfully to remote queue queue/tutorial/defaultedexample
$ mvn clean install -PRunSingleTutorial
On the server console, you will notice the following logs:
15:37:57,148 INFO [STDOUT] ---------------- 15:37:57,148 INFO [STDOUT] Received defaulted message 15:37:57,148 INFO [STDOUT] ---------------- 15:37:57,210 INFO [STDOUT] ---------------- 15:37:57,210 INFO [STDOUT] Received message 15:37:57,210 INFO [STDOUT] ----------------
You configure properties by using the <message-driven> element and sub elements which correspond to the
@ActivationConfigProperty
annotation.
ejb-jar.xml:
<message-driven> <ejb-name>ExampleMDB</ejb-name> <ejb-class>org.jboss.tutorial.mdb_deployment_descriptor.bean.ExampleMDB</ejb-class> <transaction-type>Bean</transaction-type> <message-destination-type>javax.jms.Queue</message-destination-type> <activation-config> <activation-config-property> <activation-config-property-name>acknowledgeMode</activation-config-property-name> <activation-config-property-value>AUTO_ACKNOWLEDGE</activation-config-property-value> </activation-config-property> </activation-config> </message-driven>
jboss.xml
<message-driven> <ejb-name>ExampleMDB</ejb-name> <destination-jndi-name>queue/tutorial/example</destination-jndi-name> </message-driven>
The queue/tutorial/example
is configured through the queue-example-service.xml
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "mdb_deployment_descriptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Message sent successfully to remote queue.
$ mvn clean install -PRunSingleTutorial
On the server console, you will notice the following logs:
23:37:38,175 INFO [STDOUT] ---------------- 23:37:38,175 INFO [STDOUT] Received message 23:37:38,175 INFO [STDOUT] ----------------
This example shows a bunch of things. First, it introduces the @Column
annotation.
It also shows how entities can be detached and re-attached to persistence storage using the EntityManager.merge()
.
It also shows some basic queries.
EJB 3.0 has a complete Object/Relational mapping. You can use the @Column
annotation to specify
which column in the table your property should map to. Take a look at the org.jboss.tutorial.merge.bean.Customer
entity.
@Column(name = "FIRST") public String getFirst() { return first; }
The EntityManager
service has a built in find by primary key method:
<T> find(Class<T>, Object pk)
The org.jboss.tutorial.merge.bean.CustomerDAOBean
stateless EJB's find()
method
wraps remote calls to the EntityManager.
public Customer find(int id) { return manager.find(Customer.class, id); }
EntityManager
allows you to create query objects on the fly that can be reused over and over,
or just one time. Queries also support named parameters. The org.jboss.tutorial.merge.bean.CustomerDAOBean
reflects this usage in the findByLastName
method.
public List findByLastName(String name) { return manager.createQuery("from Customer c where c.last = :name").setParameter("name", name).getResultList(); }
The Value Object pattern is built into EJB 3.0. You can detach an object from persistence storage and send it across
the network to the client. The client can make updates locally to the object, send it back to the server and the changes
can be merged/synchronized back to the database using the EntityManager.merge()
method.
This is exactly what the org.jboss.tutorial.merge.client.Client
does.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "merge" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
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
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
EJB3 beans may reference EJB2.1 and vice versa. This tutorial demonstrates mechanisms for such references.
To start, take a look at the EJB3 SLSB org.jboss.tutorial.reference21_30.bean.Stateless3Bean
,
the EJB2.1 SLSB org.jboss.tutorial.reference21_30.bean.Stateless2Bean
and the deployment descriptors for the EJB2.1 SLSB ejb21_app/src/main/resources/META-INF/ejb-jar.xml
and
ejb21_app/src/main/resources/META-INF/jboss.xml
:
<session> <ejb-name>ejb/Stateless2</ejb-name> <home>org.jboss.tutorial.reference21_30.bean.Stateless2Home</home> <remote>org.jboss.tutorial.reference21_30.bean.Stateless2</remote> <ejb-class>org.jboss.tutorial.reference21_30.bean.Stateless2Bean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <ejb-ref> <ejb-ref-name>ejb/Stateless3</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>org.jboss.tutorial.reference21_30.bean.Stateless3Home</home> <remote>org.jboss.tutorial.reference21_30.bean.Stateless3</remote> </ejb-ref> </session>
Notice, the ejb-jar.xml use a 2.x dtd for this EJB2.x application:
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
The ejb-ref
element references the 3.0 EJB. This mapping will make available the Stateless3
bean
in the ENC (java:comp/env) namespace of the Stateless2
EJB.
Observe carefully, the use of <home>
and <remote>
in the ejb-ref
.
Remember that we can provide a EJB2.1 view for an EJB3 bean. See the "ejb21_client_adaptors" for more details. We have intentionally
exposed the home and remote view for the EJB3 Stateless3Bean so that it can be referenced by a EJB2.1 bean, without which the EJB2.1
bean would not have been able to reference this bean through its local ENC (because the home/remote are mandatory for a ejb-ref for EJB2.1 app)
@Stateless @RemoteHome(Stateless3Home.class) public class Stateless3Bean { ...
public interface Stateless3Home extends EJBHome { public Stateless3Remote create() throws java.rmi.RemoteException, javax.ejb.CreateException; ...
Two mechanisms for referencing EJB2.1 from a EJB3 are demonstrated. The first mechanism uses annotations exclusively.
Note the @EJBs
annotation on org.jboss.tutorial.reference21_30.bean.Stateless3Bean
.
The name
parameter for the @EJB
specifies the name with which the 2.1 EJB will be bound
in the ENC (java:comp/env) namespace of the Stateless3Bean
. The mapped-name
parameter
specifies the global JNDI binding of the 2.1 EJB.
@EJBs( {@EJB(name = "injected/Stateless2", mappedName = "Stateless2")}) public class Stateless3Bean { ...
The testAccess
method in the Stateless3Bean
looks up this EJB2.1 using the
ENC jndi-name:
Stateless2Home home = (Stateless2Home) jndiContext.lookup(Container.ENC_CTX_NAME + "/env/injected/Stateless2"); Stateless2 test2 = home.create();
Also note that since the bean being looked up is a EJB2.1 bean, the lookup will return the home interface of the bean.
The second mechanism of referencing a EJB2.1 bean in a EJB3 bean is through the deployment descriptors. Take a look at the
ejb3_app/src/main/resources/META-INF/ejb-jar.xml
and ejb3_app/src/main/resources/META-INF/jboss.xml
<session> <ejb-name>Stateless3Bean</ejb-name> <ejb-ref> <ejb-ref-name>ejb/Stateless2</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> </ejb-ref> </session>
The ejb-jar.xml should use the ejb-jar 3.0 xsd:
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd" version="3.0">
This binds the Stateless2 bean to the ENC (java:comp/env) namespace of the Stateless3Bean. Also take a look at the
ejb3_app/src/main/resources/META-INF/jboss.xml
which maps the global jndi name of the Stateless2
bean with the ejb-ref-name.
<session> <ejb-name>Stateless3Bean</ejb-name> <ejb-ref> <ejb-ref-name>ejb/Stateless2</ejb-ref-name> <jndi-name>Stateless2</jndi-name> </ejb-ref> </session>
This reference is then used to inject the Stateless2 bean in the Stateless3Bean using @EJB
annotation:
@EJB (name="ejb/Stateless2") private Stateless2Home stateless2Home;
org.jboss.tutorial.reference21_30.bean.Stateless3Bean
also exposes a business-remote interface, so
that the org.jboss.tutorial.reference21_30.servlet.EJBReferenceServlet
can use the business-remote interface.
@Stateless @Remote(Stateless3.class) @RemoteBinding(jndiBinding = "Stateless3") public class Stateless3Bean { ...
There's a very important difference between the remote
and a business-remote
interface. The EJB2.x remote interfaces, which extend from EJBObject, are referred through the <remote>
tag in the ejb-jar.xml. On the other hand, the EJB3 style Plain Old Java Interface which is implemented by your EJB3 style
POJO bean is known as the business-remote interface and is represented by the @Remote
and it's
corresponding <business-remote>
tag in ejb-jar.xml.
Similar is the case with <local>
and the <business-local>
tags in ejb-jar.xml.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "reference21_30" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant
This will deploy the ear into the JBossAS-5.x server ("default" configuration).
Test
button to check that the EJB2.1 and EJB3 beans are referencing
each other.
$ mvn clean install -PRunSingleTutorial
This will create the EAR in the target
folder of the tutorial. Copy this EAR to the deploy folder of JBossAS-5.x
and start the server (if it's already not started). Then follow the steps mentioned above, to access the servlet from the web browser.
The "entity" tutorial only showed one-to-many and many-to-one relationships. This tutorial will show you one-to-one and many-to-many relationships.
There is a unidirectional one-to-one relationship defined between the org.jboss.tutorial.relationships.bean.Customer
and org.jboss.tutorial.relationships.bean.Address
. Customer defines the uni-directional relationship.
@OneToOne(cascade = {CascadeType.ALL}) @JoinColumn(name = "ADDRESS_ID") public Address getAddress() { return address; }
CascadeType.ALL
specifies that when a Customer is created, if there is any
Address association, then that Address will be created as well(CascadeType.PERSIST
).
If the Customer is deleted from persistence storage, the Address will be deleted(CascadeType.REMOVE
).
If a Customer instance is re-attached to persistence storage, any changes to the Address collection will be merged with
persistence storage (CascadeType.MERGE
).
There is a many-to-many relationship between org.jboss.tutorial.relationships.bean.Customer
and org.jboss.tutorial.relationships.bean.Flight
. In order to have a many-to-many relationship
there needs to be a distinct join table that maps the many-to-many relationship. This is called an association table.
You can have JBoss automatically generate the association table for you, or you can use the @JoinTable
annotation to define it yourself. If you use @JoinTable
it must be defined on both sides of the
bi-directional relationship. Let's look at the Customer side of the relationship:
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER, mappedBy="customers")
The mappedBy
attribute states that the Flight.customers
property is responsible
for mapping and managing the relationship. The spec allows for multiple join and inverse join columns.
See the "Composite Primary Key" tutorial for more information.
Let's look at the other side of the relationship in Flight.
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER) @JoinTable(table = @Table(name = "flight_customer_table"), joinColumns = {@JoinColumn(name = "FLIGHT_ID")}, inverseJoinColumns = {@JoinColumn(name = "CUSTOMER_ID")}) public Set<Customer> getCustomers() { return customers; }
The database associate table will look like this:
create table FLIGHT_CUSTOMER_TABLE ( CUSTOMER_ID integer, FLIGHT_ID integer );
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "relationships" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Air France customers [java] Bill [java] Monica [java] USAir customers [java] Molly
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
Resources (e.g. data sources, JavaMail sessions, JMS queues) may be added to the local jndi namespace (ENC) of individual EJBs. This is to separate the jndi names used in the bean code from the global jndi bindings set by the bean deployer. The mapping of the bean local jndi binding and the global binding may be handled via the ejb-jar.xml and jboss.xml deployment descriptors.
Take a look at META-INF/ejb-jar.xml
. For ENCBean
, there are 3
<resource-ref>
elements indicating resource reference names and types.
Take a look at META-INF/jboss.xml
. For ENCBean
, there are again
3 <resource-ref>
elements indicating resource reference names and either the global jndi
binding via the <jndi-name>
element or the resource name. Resource managers
are used to map resource names to global jndi bindings via the <resource-managers>
element.
Take a look at org.jboss.tutorial.resource_ref.bean.TestENCBean
. Each one of the resources are accessed from the
bean local jndi namespace (i.e. java:comp/env) by the value set in the <res-ref-name>
values
in the deployment descriptors.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "resource_ref" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Successfully accessed bean resource references
$ mvn clean install -PRunSingleTutorial
On the server you will notice these logs:
13:44:09,500 INFO [TestENCBean] Found data source resource ref 13:44:09,500 INFO [TestENCBean] Found mail resource ref 13:44:09,500 INFO [TestENCBean] Found jms queue resource ref 13:44:09,500 INFO [TestENCBean] Found jms queue resource env ref
The EJB specification allows you to map an entity bean to multiple tables. You do this by using the @SecondaryTable
annotation.
The org.jboss.tutorial.secondary.bean.Customer
entity maps its address properties to a
separate ADDRESS table. The first thing it does is define the secondary table.
@Entity @Table(name = "CUSTOMER") @SecondaryTable(name = "EMBEDDED_ADDRESS", join = {@JoinColumn(name = "ADDRESS_ID")}) public class Customer implements java.io.Serializable { }
The @JoinColumn
of the secondary table must match the value of the Customer's primary key. To map
individual properties to a secondary table you use the secondaryTable
member value of @Column
.
@Column(name = "STREET", secondaryTable = "EMBEDDED_ADDRESS") public String getStreet() { return street; }
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "secondary" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
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
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
The EJB 3.0 specification has made the XML deployment descriptors optional. This tutorial goes over how to use the transaction and security annotations of EJB 3.0.
Using transactions is easy, just use the <listing>javax.ejb.TransactionAttribute</listing> annotation.
The javax.ejb.TransactionAttributeType
enum has every transactional type. Here's an example
for using REQUIRES_NEW transaction type:
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public int add(int x, int y) { return x + y; }
Take a look at org.jboss.tutorial.security.bean.CalculatorBean
. The @javax.annotation.security.RolesAllowed
and @javax.annotation.security.PermitAll
are the EJB 3.0 security annotations. You can attach a method permission to any method
and define which roles are allowed to invoke on that method. The javax.ejb.RunAs
annotation can also be applied at the class
level. There is also an additional JBoss specific annotation that you must supply at the class level @org.jboss.ejb3.annotation.SecurityDomain
.
The @SecurityDomain
specifies the JAAS application-policy name which will be used by JBoss to authenticate and authorize.
See the JBoss Application Server documentation for more details. In this particular example, the "other" domain is used.
The "other" domain corresponds to a users.properties and roles.properties files that contain cleartext user, password, and user/role associations.
If you open the tutorial jar file you will see these two files in there.
Open up org.jboss.tutorial.security.client.Client
. You'll see that it looks up the stateless bean.
Also notice that there is no Home interface and you can begin executing on the stateless bean right away.
The client uses a JBoss's SecurityClient class to pass the user name and password:
import org.jboss.security.client.SecurityClient; import org.jboss.security.client.SecurityClientFactory; SecurityClient securityClient = SecurityClientFactory.getSecurityClient(); securityClient.setSimple("kabir", "invalidpassword"); securityClient.login();
See the documentation of org.jboss.security.client.SecurityClient for more options
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "security" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Kabir is a student. [java] Kabir types in the wrong password [java] Authentication exception, principal=kabir [java] Kabir types in correct password. [java] Kabir does unchecked addition. [java] 1 + 1 = 2 [java] Kabir is not a teacher so he cannot do division [java] Insufficient method permissions, principal=kabir, interface=org.jboss.ejb3.EJBContainerInvocation, requiredRoles=[teacher], principalRoles=[student] [java] Students are allowed to do subtraction [java] 1 - 1 = 0
$ mvn clean install -PRunSingleTutorial
If you want to change the roles for the user, through the roles.properties, you will have to restart the server, for the role changes to take effect. This is because by default JBoss caches the roles for a user and until the cache is flushed, either through this configuration or through server restart, the changes won't take effect.
Service POJOs allow you to define POJOs as JBoss services. The way you define them is very similar to how you define stateless or stateful session beans. One very important difference is that there will only ever be ONE instance of the service bean. i.e. it is not pooled - the bean instance is a singleton. The singleton bean contains shared state, so data set by one client is accessible by other clients.
Take a look at org.jboss.tutorial.service.bean.ServiceOne.java
.
It has been annotated with @org.jboss.ejb3.annotation.Service
, this defines it as a
singleton service in JBoss. It implements org.jboss.tutorial.service.bean.ServiceOneRemote
and org.jboss.tutorial.service.bean.ServiceOneLocal
just as you would do for a normal stateful/stateless bean.
ServiceOne also implements org.jboss.tutorial.service.bean.ServiceOneManagement
.
org.jboss.tutorial.service.bean.ServiceOne
has been annotated with @org.jboss.ejb3.annotation.Management
.
JBoss will inspect this interface, and create and install an MBean implementing the attributes and operations defined in the @Management
interface. The MBean will work on the same singleton bean instance as the remote and local interfaces.
Just as for "normal" MBeans in JBoss, the @Service
supports lifecycle management. Lifecycle management
consists of two things:
org.jboss.tutorial.service.bean.ServiceOneManagement
contains the four methods:
void create() throws Exception; void start() throws Exception; void stop(); void destroy();
You do not need to include all these methods, you can pick and choose. If present, the service container will call these methods as follows:
create
: called by the server when the service is created and all the services it depends
upon have been created too. At this point the service (and all the services it depends on) is installed in the
JMX server, but is not yet fully functional.
start
: called by the server when the service is started and all the services it depends upon
have been started too. At this point the service (and all the services it depends on) is fully functional.
stop
: called by the server when the service is stopped. At this point the service
(and all the services that depend on it) is no longer fully operational.
destroy
: called by the server when the service is destroyed and removed from the MBean server.
At this point the service (and all the services that depend on it) are destroyed.
Let's take a look at how to define the dependencies between services. Open org.jboss.tutorial.service.bean.ServiceTwo
.
Again it has been annotated with @Service, and it implements the @Management annotated interface org.jboss.tutorial.service.bean.ServiceTwoManagement
.
@Service(objectName = ServiceTwo.OBJECT_NAME) @Management(ServiceTwoManagement.class) @Depends(ServiceOne.OBJECT_NAME) public class ServiceTwo implements ServiceTwoManagement { ...
The @org.jboss.ejb3.annotation.Depends
annotation specifies that this service depends on
the service created for ServiceOne. i.e. it cannot be started until the service created for ServiceOne has been started.
You can specify an array of ObjectName
s if you depended on more than one service.
You will also notice the use of objectName
property of @Service. This is used to install the service
under a custom ObjectName instead of the default ObjectName. So in this tutorial, our ServiceTwo will be registered at
tutorial:service=ServiceTwo
Take a look at org.jboss.tutorial.bean.ServiceThree
. It has dependencies on other MBeans, but rather than
annotating the class with @Depends, the dependencies are specified on fields and setter methods.
@Depends(ServiceOne.OBJECT_NAME) public ObjectName serviceOneName; private ServiceTwoManagement service2; @Depends(ServiceTwo.OBJECT_NAME) public void setServiceTwo(ServiceTwoManagement service2) { this.service2 = service2; }
With regard to the lifecycle dependencies, the effect of annotating fields and setters with @Depends is the same
as if we annotated the class. So, ServiceThree cannot be started until ServiceOne (tutorial:service=ServiceOne
) and ServiceTwo(tutorial:service=ServiceTwo
)
are started. Annotating the fields and setters with @Depends though, allows you to inject the dependencies. So in this tutorial,
the ServiceThree.serviceOneName
will be injected with the ObjectName
which corresponds to
ServiceOne. More interesting is the injection of ServiceTwo. setServiceTwo()
takes a parameter, which is the
ServiceTwoManagement
management interface of ServiceTwo. The server creates a dynamic proxy for
the ServiceTwoManagement interface, and injects that. This means that you can call the methods on the service2
field without caring that you are actually invoking methods on another service.
You can define interceptors for your service beans in the same way as shown in the "interceptors" tutorial. This example defines one in the ServiceThree bean class itself:
@AroundInvoke public Object intercept(InvocationContext ctx) throws Exception { System.out.println("ServiceThree - Interceptor"); return ctx.proceed(); }
You can deploy a Service bean as an XMBean, where the management attributes and operations are defined via xml.
Take a look at org.jboss.tutorial.service.bean.XMBeanService
. Note the @Service annotation
specifies an xmbean
property. Also note there is no @Management annotation.
@Service(objectName = XMBeanService.OBJECT_NAME, xmbean = "resource:META-INF/service-xmbean.xml") @Remote(XMBeanServiceRemote.class) public class XMBeanService implements XMBeanServiceRemote { ...
Now take a look at META-INF/service-xmbean.xml
. This is the file referenced by the xmbean
property and specifies the bean's management attributes and operations. Note the class
, constructor
,
attribute
and operation
elements.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "service" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure the "default" server configuration of JBossAS-5.x is running
$ ant $ ant run run: [java] invoking remote business interface of ServiceOne... [java] Set the attribute value through ServiceOneRemote to 100 [java] attribute value for (ServiceOne) singleton obtained via JMX is what we set via remote interface: 100 [java] Invoking ServiceThree via JMX... [java] Hello from service One [java] ServiceThree - Calling ServiceTwo.sayHello() via MBean proxy [java] invoking XMBean (configured through deployment descriptor)... [java] Set the attribute value to 50 [java] Invoking XMBean through JMX [java] attribute value for (XMBeanService deployment descriptor configured) singleton obtained via JMX is what we set via remote interface: 50 [java] Hello from an XMBean
$ mvn clean install -PRunSingleTutorial
On the server side when the application is deployed, you will notice these logs:
16:56:54,036 INFO [STDOUT] ServiceOne - Created ... 16:56:54,082 INFO [STDOUT] ServiceOne - Started ... 16:56:54,142 INFO [STDOUT] ServiceTwo - Started ... 16:56:54,226 INFO [STDOUT] ServiceThree - Started
Notice that the order is maintained because of the dependencies we have configured on the @Service.
This tutorial is similar to the "service" tutorial which shows how to use Service POJOs in JBoss. In this tutorial we will use deployment descriptors instead of annotations to configure the services.
Take a look at org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOne
and the corresponding deployment
descriptor META-INF/jboss.xml
. The <service>
tag defines it as a
singleton service in JBoss. It implements org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOneRemote
and org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOneLocal
using the <business-remote>
and <business-local>
tags.
ServiceOne also implements org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOneManagement
identified through the <management>
tag. JBoss will inspect this interface, and create and
install an MBean implementing the attributes and operations defined in the interface. The MBean will work on the same
singleton bean instance as the remote and local interfaces.
The META-INF/jboss.xml
also shows that the default ObjectName of the service can be overriden
using the <object-name>
tag:
<service> <ejb-name>ServiceOne</ejb-name> <ejb-class>org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOne</ejb-class> <business-local>org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOneLocal</business-local> <business-remote>org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOneRemote</business-remote> <object-name>tutorial:service=serviceOne</object-name> <management>org.jboss.tutorial.service_deployment_descriptor.bean.ServiceOneManagement</management> <jndi-name>serviceOne/remote</jndi-name> <local-jndi-name>serviceOne/local</local-jndi-name> </service>
Take a look at org.jboss.tutorial.service_deployment_descriptor.bean.ServiceThree
and
org.jboss.tutorial.service_deployment_descriptor.bean.ServiceTwo
where we use the @Depends
annotation to add dependencies between services.
For a detailed explanation of @Depends in @Service, take a look at our "service" tutorial.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "service_deployment_descriptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure the "default" server configuration of JBossAS-5.x is running
$ ant $ ant run run: [java] attribute value for singleton obtained via JMX is what we set via remote interface: 100 [java] Hello from service One [java] Hello from service Two
$ mvn clean install -PRunSingleTutorial
On the server side when the application is deployed, you will notice these logs:
17:37:17,869 INFO [STDOUT] ServiceOne - Started ... 17:37:17,910 INFO [STDOUT] ServiceTwo - Started ... 17:37:17,949 INFO [STDOUT] ServiceThree - Started
Notice that the order is maintained because of the dependencies we have configured on the @Service.
The EJB specification allows you to define entities that inherit from one another. The inheritance relationships can be reflected in queries as well. So, if you queried based on the base class, the query is polymorphic.
The tutorial example uses the single table strategy to map an inheritance relationship of org.jboss.tutorial.singleinheritance.bean.Pet
,
which is the base class for org.jboss.tutorial.singleinheritance.bean.Cat
and org.jboss.tutorial.singleinheritance.bean.Dog
.
With the single table strategy, the entire class hierarchy is persisted in one big single table. A discriminator column is required to
differentiate between which class type is persisted in a particular row. This is what the annotations look like for Pet.
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "ANIMAL_TYPE", discriminatorType = DiscriminatorType.STRING) public class Pet implements java.io.Serializable { }
The @DiscriminatorColumn
specifies the column that will hold the type of the persisted entity.
For subclasses, they must define the value of the discriminator column that will identify the class.
Here's the Dog entity which extends Pet:
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING) @DiscriminatorValue("DOG") public class Dog extends Pet { }
org.jboss.tutorial.singleinheritance.bean.PetDAOBean
stateless EJB wraps some polymorphic queries.
public List findByWeight(double weight) { return manager.createQuery("from Pet p where p.weight < :weight").setParameter("weight", weight).getResultList(); }
Even though the <listing>findByWeight</listing> method queries on Pet, either Dog or Cat instances can be returned.
The table mapping for this example looks like this:
create table PET ( ID integer primary key, ANIMAL_TYPE varchar, NAME varchar, WEIGHT double, LIVES int, NUMBONES int );
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "singleinheritance" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Sox [java] Junior
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
Take a look at the META-INF/ejb-jar.xml
and org.jboss.tutorial.stateful_deployment_descriptor.bean.ShoppingCartBean
.
You specify a stateful bean with the "session" and "session-type" tags. Note that all bean types in EJB 3.0 are homeless,
so there is no requirement for a "home" or "local-home" tag. The bean class is specified with the "ejb-class" tag.
ShoppingCartBean also implements a business-remote interface. Take a look at org.jboss.tutorial.stateful_deployment_descriptor.bean.ShoppingCart
.
To define this as the business-remote interface of ShoppingCartBean you need to use the "business-remote" tag in the ejb-jar.xml. Here's the META-INF/ejb-jar.xml
:
<session> <ejb-name>ShoppingCart</ejb-name> <business-remote>org.jboss.tutorial.stateful_deployment_descriptor.bean.ShoppingCart</business-remote> <ejb-class>org.jboss.tutorial.stateful_deployment_descriptor.bean.ShoppingCartBean</ejb-class> <session-type>Stateful</session-type> <remove-method> <bean-method> <method-name>checkout</method-name> </bean-method> <retain-if-exception>false</retain-if-exception> </remove-method> <transaction-type>Container</transaction-type> </session>
There's a very important difference between the remote
and a business-remote
interface. The EJB2.x remote interfaces, which extend from EJBObject, are referred through the <remote>
tag in the ejb-jar.xml. On the other hand, the EJB3 style Plain Old Java Interface which is implemented by your EJB3 style
POJO bean is known as the business-remote interface and is represented by the @Remote
and it's
corresponding <business-remote>
tag in ejb-jar.xml.
Similar is the case with <local>
and the <business-local>
tags in ejb-jar.xml.
Take another look at META-INF/ejb-jar.xml
. Look for the "remove-method" tag.
Instead of explicitly calling EJBObject.remove() in your applications and thus polluting it further with J2EE specific code,
any method specified in the "remove-method" tag will cause the stateful bean instance to be removed from the container
at the end of the method call. This deployment descriptor behavior mimics the @Remove
annotation.
The CalculatorBean will have its remote interface bound in JNDI. Take a look at META-INF/jboss.xml
.
Note the jndi-name
tag. This specifies the jndi binding for the remote interface of the bean.
Open up org.jboss.tutorial.stateful_deployment_descriptor.client.Client
.
You'll see that it looks up the stateful bean under its jndi name. Also notice that there is no Home interface
and you can begin executing on the stateful bean right away. When you access the bean in JNDI, an instance of the stateful
bean will be created on the server. So, when you need a different instance of the stateful bean, you do an additional
jndi lookup to get this new reference.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "stateful_deployment_descriptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Buying 1 memory stick [java] Buying another memory stick [java] Buying a laptop [java] Print cart: [java] 2 Memory stick [java] 1 Laptop [java] Checkout [java] Should throw an object not found exception by invoking on cart after @Remove method [java] Successfully caught no such object exception.
$ mvn clean install -PRunSingleTutorial
CalculatorBean is defined as a stateless session bean through the <session>
and
<session-type>
elements. This marks the class as a stateless bean and the deployer will
deploy that class as a stateless bean EJB container.
CalculatorBean also implements two interfaces. One is the business-remote interface of the EJB the other is the business-local
interface. Take a look at org.jboss.tutorial.stateless_deployment_descriptor.bean.CalculatorRemote
. To define this
as the business-remote interface of Calculator bean you specify the interface with the <remote>
tag.
Similarly for org.jboss.tutorial.stateless_deployment_descriptor.bean.CalculatorLocal
you need to specify the
business-local interface with the <local>
tag. Here's the META-INF/ejb-jar.xml
:
<session> <ejb-name>Calculator</ejb-name> <business-local>org.jboss.tutorial.stateless_deployment_descriptor.bean.CalculatorLocal</business-local> <business-remote>org.jboss.tutorial.stateless_deployment_descriptor.bean.CalculatorRemote</business-remote> <ejb-class>org.jboss.tutorial.stateless_deployment_descriptor.bean.CalculatorBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session>
There's a very important difference between the remote
and a business-remote
interface. The EJB2.x remote interfaces, which extend from EJBObject, are referred through the <remote>
tag in the ejb-jar.xml. On the other hand, the EJB3 style Plain Old Java Interface which is implemented by your EJB3 style
POJO bean is known as the business-remote interface and is represented by the @Remote
and it's
corresponding <business-remote>
tag in ejb-jar.xml.
Similar is the case with <local>
and the <business-local>
tags in ejb-jar.xml.
The Calculator bean will have two JNDI bindings for the remote and Local interface. The META-INF/jboss.xml
through the <jndi-name>
and the <local-jndi-name>
specifies the jndi-name
for the remote and the local interfaces, respectively:
<session> <ejb-name>Calculator</ejb-name> <jndi-name>org.jboss.tutorial.stateless_deployment_descriptor.bean.CalculatorRemote</jndi-name> <local-jndi-name>org.jboss.tutorial.stateless_deployment_descriptor.bean.CalculatorLocal</local-jndi-name> </session>
Open up org.jboss.tutorial.stateless_deployment_descriptor.client.Client
.
The client looks up the bean using the jndi-name specified in the jboss.xml. Also notice that there is no Home interface
and you can begin executing on the stateless bean right away.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "stateless_deployment_descriptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] 1 + 1 = 2 [java] 1 - 1 = 0
$ mvn clean install -PRunSingleTutorial
The EJB3 specification allows you to define entities that inherit from one another. The inheritance relationships can be reflected in queries as well. So, if you queried based on the base class, the query is polymorphic.
This tutorial uses the table per class strategy to map an inheritance relationship of
org.jboss.tutorial.tableperinheritance.bean.Pet
, which is the base class for
org.jboss.tutorial.tableperinheritance.bean.Cat
and org.jboss.tutorial.tableperinheritance.bean.Dog
.
With the table per class strategy there is a table per class in the hierarchy, and each table has every single property that particular class will persist.
This is what the annotations look like for Pet:
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Pet implements java.io.Serializable { ...
The subclass annotations look like this:
@Entity public class Dog extends Pet { ...
org.jboss.tutorial.tableperinheritance.bean.PetDAOBean
stateless EJB wraps some polymorphic queries :
public List findByWeight(double weight) { return manager.createQuery("from Pet p where p.weight < :weight").setParameter("weight", weight).getResultList(); }
Even though the findByWeight
method queries on Pet, either Dog or Cat instances can be returned.
The table mapping for this example looks like this:
create table CAT ( ID integer primary key, LIVES int NAME varchar, WEIGHT double ); create table DOG ( ID integer primary key, NUMBONES int NAME varchar, WEIGHT double );
The table per class strategy is less efficient than the single table strategy as the SQL query is more complicated.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
Because of a bug in HSQL, this tutorial does not work against the HSQL DB (which JBoss ships by default). Till this is fixed, running the client is not possible. Alternately, you can configure a datasource to use some other database and then use that datasource in the persistence.xml
From the command prompt, move to the "tableperinheritance" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure the "default" server configuration of JBossAS-5.x is running
$ ant
$ mvn clean install -PRunSingleTutorial
You can view the tables created by JBoss by going to the
Hypersonic Service,
scrolling down to the startDatabaseManager
button and clicking it.
A Hypersonic SQL window will be minimized, but you can open it up to look at the tables and do queries.
This example shows you how to access javax.ejb.SessionContext
as well as using
the EJB Timer Service. It also explains how callbacks work in EJB 3.0.
The javax.ejb.SessionContext
is injected using the @javax.annotation.Resource
annotation.
When the stateless bean instance is created the field will be initialized with the correct SessionContext.
Take a look at org.jboss.tutorial.timer.bean.ExampleTimerBean
private @Resource SessionContext ctx;
The rest of the bean example registers a timer with the EJB Timer service. In the EJB 2.1 specification
it was required to implement an interface to get ejbTimeout callbacks. In JBoss EJB3, it is implemented
as an annotation. All you have to define is a method annotated with javax.ejb.Timeout
.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "timer" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Timer scheduled to trigger after 5 seconds
$ mvn clean install -PRunSingleTutorial
On the server you will notice these logs:
INFO [STDOUT] --------------------- INFO [STDOUT] Created a timer event to be triggered after 5000 milli seconds INFO [STDOUT] --------------------- INFO [STDOUT] --------------------- INFO [STDOUT] * Received Timer event: Hello World INFO [STDOUT] ---------------------
Persistent classes that are mapped using Hibernate *.hbm.xml files are supported in JBoss. The EJB3 Deployer will search the archive
for any .hbm.xml
files and add them to the definition of the underlying Hibernate SessionFactory. These
.hbm.xml
files can be virtually anywhere within the archive under any java package or directory.
Take a look at the customer.hbm.xml
for an example.
Class mappings defined in .hbm.xml
files can be managed by EntityManagers just as annotated
@Entity beans are. Also, you are allowed to have relationships between a .hbm.xml
mapped class and an EJB3 entity. So, mixing/matching is allowed. Which means you can have some entities defined in .hbm.xml and
some others through @Entity annotations.
You can inject a org.hibernate.Session
and org.hibernate.SessionFactory
directly into your EJBs just as you can do with EntityManagers and EntityManagerFactorys. The behavior of a Session is
just the same as the behavior of an injected EntityManager. The application server controls the lifecycle of the
Session so that you do not have to open, flush, or close the session. Extended persistence contexts also work
with injected Hibernate Sessions.
import org.hibernate.Query; import org.hibernate.Session; @Stateless @Remote(CustomerRemote.class) @RemoteBinding(jndiBinding = "CustBean") public class CustomerBean implements CustomerRemote { @PersistenceContext private Session session;
Take a look at org.jboss.tutorial.hibernate.bean.CustomerBean
for more details.
You can get access to the current underlying Hibernate Session by calling the getDelegate
method on the
EntityManager :
@Stateless @Remote(CustomerRemote.class) @RemoteBinding (jndiBinding="AnotherCustBean") public class AnotherCustomerBean implements CustomerRemote { @PersistenceContext private EntityManager em; public Customer getCustomer(long id) { org.hibernate.Session session = (Session) em.getDelegate(); return (Customer) session.get(Customer.class, id); } ...
Take a look at org.jboss.tutorial.hibernate.bean.AnotherCustomerBean
for more details.
You can get access to the current underlying Hibernate Query by typecasting your reference to a org.hibernate.ejb.QueryImpl
.
public List<Customer> getCustomers(String fname) { org.hibernate.ejb.QueryImpl queryImpl = (QueryImpl) em.createQuery("from Customer where fname ='" + fname + "'"); org.hibernate.Query query = queryImpl.getHibernateQuery(); return query.list(); }
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "hibernate" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Jai Pai created with id = 1 [java] Jaikiran Pai created with id = 2 [java] Jai NoLastName created with id = 3 [java] Searching for customer with id = 2 [java] Found customer Jaikiran Pai with id = 2 [java] Searching for customer with id = 3 [java] Found customer Jai NoLastName with id = 3 [java] Searching for customers with first name Jai [java] Found 2 customers with first name Jai [java] Searching for customers with first name Jaikiran [java] Found 1 customers with first name Jaikiran
$ mvn clean install -PRunSingleTutorial
A JBoss extension to EJB 3.0 is that from the remote or local interface of a stateful session bean, stateless session bean or service bean you can obtain an asynchronous proxy. Methods called on the asynchronous proxy will be executed asynchronously, and the results can be obtained later on.
This JBoss specific feature of obtaining a asynchronous proxy to invoke any method on the bean asynchronously is NOT the same as the Asynchronous Methods feature in EJB 3.1. In EJB 3.1, you can mark your bean business methods to be asynchronous by using the @Asynchronous annotation.
Take a look at org.jboss.tutorial.asynch.bean.Echo
and org.jboss.tutorial.asynch.bean.EchoBean
.
They define a normal stateless session bean with a remote interface, nothing special. Now take a look at
org.jboss.tutorial.asynch.client.Client
. It shows an example of asynchronous calls on a remote interface.
We will walk through what it does here. The following lines just obtain the remote interface of the bean and call a method
following the standard synchronous usage pattern:
InitialContext ctx = new InitialContext(); Echo echo = (Echo) ctx.lookup("EchoBean/remote"); System.out.println("-------- Synchronous call"); String ret = echo.echo("normal call"); System.out.println(ret);
Next we obtain the asynchronous proxy and make a call via that. The method will be invoked asynchronously :
Echo asynchEcho = org.jboss.ejb3.common.proxy.plugins.async.AsyncUtils.mixinAsync(echo); System.out.println("-------- Asynchronous call"); ret = asynchEcho.echo("asynchronous call"); System.out.println("Direct return of async invocation is: " + ret);
We use the org.jboss.ejb3.common.proxy.plugins.async.AsyncUtils
's mixinAsync
method to
create the asynchronous proxy. All methods invoked on this proxy are invoked asynchronously, and the returned value will
always be null (or 0 in the case of numeric primitive types). In this example, the echo() method called has low overhead,
but imagine if this was a method that took a long time. In this case it would be good to be able to go ahead with some other tasks.
In Client.java, we make another call on the normal remote interface while "waiting" for the asynchronous call:
System.out.println("-------- Synchronous call"); ret = echo.echo("normal call 2"); System.out.println(ret);
Now that we have finished everything we want to do, while waiting for the asynchronus call to complete, we invoke the
getFutureResult(asynchProxy)
API on the org.jboss.ejb3.common.proxy.plugins.async.AsyncUtils
.
This will return us an instance of java.util.concurrent.Future
. To obtain the final result of the asynchronous
call, we invoke the get()
API on the returned java.util.concurrent.Future
object.
System.out.println("-------- Result of Asynchronous call"); Future<String> future = (Future<String>) AsyncUtils.getFutureResult(asynchEcho); // blocking call ret = (String) future.get(); System.out.println(ret);
The java.util.concurrent.Future.get() API is blocking and if the asynchronous operation is not yet done, it will wait for the operation to complete.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "asynch" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] -------- Synchronous call [java] normal call [java] -------- Asynchronous call [java] Direct return of async invocation is: null [java] -------- Synchronous call [java] normal call 2 [java] -------- Result of Asynchronous call [java] asynchronous call
$ mvn clean install -PRunSingleTutorial
EJB 3.0 allows for partial deployment descriptors to augment or override the behavior of source code annotations. This tutorial describes the use of partial deployment descriptors.
Beans in EJB 3.0 can be specified via source code annotations and/or a deployment descriptor. The deployment descriptor is used to
augment or override the source code annotations. There are some limitations on which annotations may be overridden, however.
The annotations that specify the bean itself (e.g. @Stateless
, @Stateful
,
@MessageDriven
, @Service
, @Consumer
) cannot be overridden.
The EJB 3.0 ejb-jar.xml
deployment descriptor xsd specifies the majority of tags as optional in order to
support annotation augmentation and overrides. The deployment descriptor does not need to specify all of the required information,
just that additional information to override or augment the source code annotations.
This section contains examples of complete and partial deployment descriptors for completely specifying or overriding specific behaviors of EJBs.
Take a look at the META-INF/ejb-jar.xml
. The ejb-jar.xml in this tutorial configures the CompleteXMLDD
bean only through the deployment descriptor. The PartialXMLDD
bean is configured through the deployment descriptor
as well as through annotations.
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd" version="3.0"> <description>Partial deployment descriptors for EJB in JBoss</description> <display-name>Partial deployment descriptors</display-name> <enterprise-beans> <session> <ejb-name>CompleteXMLDD</ejb-name> <business-remote>org.jboss.tutorial.partial_deployment_descriptor.bean.CompleteXMLDD</business-remote> <ejb-class>org.jboss.tutorial.partial_deployment_descriptor.bean.CompleteXMLDDBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <ejb-ref> <ejb-ref-name>ejb/PartialXMLDD</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <mapped-name>PartialXMLDD/remote</mapped-name> <injection-target> <injection-target-class>org.jboss.tutorial.partial_deployment_descriptor.bean.CompleteXMLDDBean</injection-target-class> <injection-target-name>partialXMLDDBean</injection-target-name> </injection-target> </ejb-ref> <resource-ref> <res-ref-name>TimerService</res-ref-name> <res-type>javax.ejb.TimerService</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> <injection-target> <injection-target-class>org.jboss.tutorial.partial_deployment_descriptor.bean.CompleteXMLDDBean</injection-target-class> <injection-target-name>timerService</injection-target-name> </injection-target> </resource-ref> <post-construct> <lifecycle-callback-class>org.jboss.tutorial.partial_deployment_descriptor.bean.ExternalCallbackListener</lifecycle-callback-class> <lifecycle-callback-method>postConstruct</lifecycle-callback-method> </post-construct> <security-identity> <run-as> <role-name>admin</role-name> </run-as> </security-identity> </session> <session> <ejb-name>PartialXMLDD</ejb-name> <business-remote>org.jboss.tutorial.partial_deployment_descriptor.bean.PartialXMLDD</business-remote> <ejb-class>org.jboss.tutorial.partial_deployment_descriptor.bean.PartialXMLDDBean</ejb-class> <session-type>Stateful</session-type> <init-method> <create-method> <method-name>create</method-name> </create-method> <bean-method> <method-name>init</method-name> </bean-method> </init-method> <remove-method> <bean-method> <method-name>remove</method-name> </bean-method> </remove-method> <transaction-type>Container</transaction-type> <env-entry> <env-entry-name>id</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>5678</env-entry-value> </env-entry> <resource-ref> <res-ref-name>DefaultDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> <injection-target> <injection-target-class>org.jboss.tutorial.partial_deployment_descriptor.bean.PartialXMLDDBean</injection-target-class> <injection-target-name>ds</injection-target-name> </injection-target> </resource-ref> </session> </enterprise-beans> <interceptors> <interceptor> <interceptor-class>org.jboss.tutorial.partial_deployment_descriptor.bean.FirstInterceptor</interceptor-class> <around-invoke> <method-name>interceptorMethod</method-name> </around-invoke> </interceptor> <interceptor> <interceptor-class>org.jboss.tutorial.partial_deployment_descriptor.bean.SecondInterceptor</interceptor-class> </interceptor> </interceptors> <assembly-descriptor> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>normal</role-name> </security-role> <method-permission> <role-name>normal</role-name> <method> <ejb-name>CompleteXMLDD</ejb-name> <method-name>sayHello</method-name> </method> </method-permission> <method-permission> <unchecked/> <method> <ejb-name>CompleteXMLDD</ejb-name> <method-name>sayBye</method-name> </method> </method-permission> <method-permission> <role-name>admin</role-name> <method> <ejb-name>PartialXMLDD</ejb-name> <method-name>echoMessage</method-name> </method> <method> <ejb-name>PartialXMLDD</ejb-name> <method-name>changeMessage</method-name> </method> </method-permission> <container-transaction> <method> <ejb-name>CompleteXMLDD</ejb-name> <method-name>greetWithNotSupportedTransaction</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>CompleteXMLDD</ejb-name> <method-name>greetWithRequiredTransaction</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <interceptor-binding> <ejb-name>CompleteXMLDD</ejb-name> <interceptor-class>org.jboss.tutorial.partial_deployment_descriptor.bean.FirstInterceptor</interceptor-class> <interceptor-class>org.jboss.tutorial.partial_deployment_descriptor.bean.SecondInterceptor</interceptor-class> </interceptor-binding> <exclude-list> <method> <ejb-name>CompleteXMLDD</ejb-name> <method-name>uncallableMethod</method-name> </method> </exclude-list> </assembly-descriptor> </ejb-jar>
The following ejb-jar.xml
file overrides any @TransactionAttribute
annotations for the
greetWithNotSupportedTransaction
method of the CompleteXMLDD
bean and adds a
@TransactionAttribute
annotation for NOT SUPPORTED
.
@TransactionAttribute (TransactionAttributeType.REQUIRES_NEW) public String greetWithNotSupportedTransaction(String name) { ... <container-transaction> <method> <ejb-name>CompleteXMLDD</ejb-name> <method-name>greetWithNotSupportedTransaction</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> ...
The following ejb-jar.xml
file creates a EJB reference and injects the EJB to the injection
target of the partialXMLDDBean
member variable.
<ejb-name>CompleteXMLDD</ejb-name> ... <ejb-ref> <ejb-ref-name>ejb/PartialXMLDD</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <mapped-name>PartialXMLDD/remote</mapped-name> <injection-target> <injection-target-class>org.jboss.tutorial.partial_deployment_descriptor.bean.CompleteXMLDDBean</injection-target-class> <injection-target-name>partialXMLDDBean</injection-target-name> </injection-target> </ejb-ref> ...
The following ejb-jar.xml
file adds a @PostConstruct
annotation to the
postConstruct
method of the CompleteXMLDD
bean.
<ejb-name>CompleteXMLDD</ejb-name> ... <post-construct> <lifecycle-callback-class>org.jboss.tutorial.partial_deployment_descriptor.bean.ExternalCallbackListener</lifecycle-callback-class> <lifecycle-callback-method>postConstruct</lifecycle-callback-method> </post-construct> ...
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "partial_deployment_descriptor" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] jai is a normal user [java] Hello, jai. I am the CompleteXMLDDBean. I have the following resources with me: [java] Timer Service : org.jboss.ejb3.timerservice.jboss.TimerServiceFacade@184a6b9 [java] PartialXMLDD Bean : Proxy to jboss.j2ee:jar=jboss-ejb3-tutorial-partial_deployment_descriptor.jar,name=PartialXMLDD,service=EJB3 implementing [interface org.jboss.ejb3.proxy.intf.EjbProxy, interface org.jboss.tutorial.partial_deployment_descriptor.bean.PartialXMLDD, interface org.jboss.ejb3.proxy.intf.StatefulSessionProxy, interface org.jboss.ejb3.proxy.intf.SessionProxy] [java] [java] Welcome jai, you are in a method with no transaction supported [java] Welcome jai, you are in a method with a REQUIRED transaction [java] Bye, jai. Hope to see you again [java] We'll try calling an uncallable method [java] Caught expected exception : Caller unauthorized [java] bill is an admin [java] Sending Hello World message to bean. We expect the bean to change it [java] This message has been changed [java] Now calling echo message [java] Hello World [java] We are done with the bean, let's remove it [java] Bean removed
$ mvn clean install -PRunSingleTutorial
The idea of Message Driven POJOs is to give a message consumer (an MDB), a typed interface that message producers can send messages through. Both the publisher and subscriber would be typed interfaces. This further facilitates the removal of all the lookups and bootstrap code you have to do to obtain and send a message and receive and dispatch a JMS message. With regular JMS you have to :
For the Message Driven POJOs, you just do:
Message Driven POJOs will have the same model as Stateless/Stateful beans. There is a bean class tagged as
@org.jboss.ejb3.annotation.Consumer
that must implement one or more @org.jboss.ejb3.annotation.Producer
interfaces. Just
as a stateless bean is tagged as @Stateless
and implements one or more
@Remote
or @Local
interfaces. Take a look at org.jboss.tutorial.consumer.bean.ExampleConsumerBean
@Consumer(activationConfig = {@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/tutorial/example")}) @Depends ("jboss.messaging.destination:service=Queue,name=tutorial") public class ExampleConsumerBean implements ExampleProducerRemote, ExampleProducerLocal, ExampleProducerXA { ... public void method1(String msg, int val) { System.out.println("method1(" + msg + ", " + val + ")"); } public void method2(String msg, Map<String, String> map) { System.out.println("method2: " + msg); for (String key : map.keySet()) { System.out.println("method2 key/val: " + key + ":" + map.get(key)); } }
Here's one of the @Producer interfaces :
@Producer public interface ExampleProducerRemote extends ExampleProducer { ...
You can see in this example that the ExampleConsumerBean
implements the @Producer interfaces and defines
the methods which can receive JMS messages. These interfaces will be used by clients(JMS Publishers) to send messages to the
consumer via JMS.
For each @Producer
interface the @Consumer
implements, there
will be a proxy that implements that @Producer
registered in JNDI under the fully qualified name of
that @Producer
interface.
Let's now look at the client org.jboss.tutorial.consumer.client.Client
public static void main(String[] args) throws Exception { InitialContext ctx = new InitialContext(); ExampleProducerRemote remote = (ExampleProducerRemote) ctx.lookup(ExampleProducerRemote.class.getName()); // you can typecast the returned proxy to obtain a ProducerManager interface that allows you to manage // interaction with JMS. ProducerManager manager = ((ProducerObject) remote).getProducerManager(); // connect - internally creates a JMS connection manager.connect(); try { // Call method1 remote.method1("Remote method1 called", 1); System.out.println("Remote method1 called"); // Call method2 Map<String, String> map = new HashMap<String, String>(); map.put("hello", "world"); map.put("great", "ejb3"); remote.method2("Remote method2 called", map); System.out.println("Remote method2 called"); } finally { // instead of typecasting, you can use a helper class that does everything for you. ProducerConfig.close(remote); }
When the @Consumer
is deployed by the EJB3 container, it looks for all of its @Producer
interfaces
and registers each one of them in JNDI under their fully qualified class name.
The client looks up the ExampleProducerRemote
from the JNDI and uses the returned proxy to send the message.
The returned proxy can be cast to org.jboss.ejb3.mdb.ProducerObject
. It then gets a org.jboss.ejb3.mdb.ProducerManager
,
that manages the JMS connection for this proxy. To start being able to send messages to the Queue, the client calls connect
on the
ProducerManager
. When the client calls method1()
on the proxy, this method call is converted
into a JMS message and published to the Queue of the Consumer. The consumer will receive the message and invoke its method1
method.
The proxy registered in JNDI will know how to contact the JMS Queue/Topic to publish messages. You can specify explicitly through
the connectionFactory
attribute of the @Producer
annotation what the JMS ConnectionFactory
JNDI name is, or you can rely on defaults.
The default value for the ConnectionFactory JNDI name is "ConnectionFactory". If you additionally tag the producer as @ProducerLocal instead of @Producer, then "java:/ConnectionFactory" will be used.
If you tag a producer as @ProducerLocal, the proxy will lookup the connection factory via the default InitialContext when connect() is called. Otherwise, the ConnectFactory reference will be embedded directly within the proxy.
The methods defined in a Producer are turned into JMS messages. The default message properties are a Time To Live of 0, a Priority of 4, and a delivery mode of PERSISTENT. You can override these default values in a couple of ways.
@Producer @MessageProperties(delivery=DeliveryMode.NON_PERSISTENT, timeToLive=1000, priority=1) public interface ExampleProducer { ...In this configuration, all method calls on ExampleProducer will use the JMS message properties defined with the
@MessageProperties
annotation on the interface.
public interface ExampleProducer { void method1(String msg, int val); @MessageProperties(delivery = DeliveryMode.NON_PERSISTENT) void method2(String msg, Map<String, String> map); }So, in the above example,
method1()
uses the default message properties, and
method2()
overrides the defaults via the @MessageProperties
annotation attached to it.
Sometimes you may need to access the real JMS message. Maybe you need to obtain the replyTo destination or set an
acknowledgement or something. You can obtain it by using the @org.jboss.ejb3.annotation.CurrentMessage
annotation.
@CurrentMessage private Message currentMessage;
This annotation will inject the current JMS message into your Consumer bean before your target method is invoked.
To build and run the example, make sure you have installed JBoss 5.x. See the Section 1.1, “JBoss Application Server 5.x” for details.
From the command prompt, move to the "consumer" folder under the Section 1.3, “Set the EJB3_TUTORIAL_HOME”
Make sure your JBossAS-5.x is running
$ ant $ ant run run: [java] Remote method1 called [java] Remote method2 called
$ mvn clean install -PRunSingleTutorial