The EJB 3.1 specification outlines these features for the embeddable container:
- Synchronous calling of local stateless, stateful, and singleton beans (singletons are new in EJB 3.1)
- Declarative and programmatic security
- Interceptors
- Use of annotations or XML deployment descriptors
- Support for JPA 2.0
With the exception of an optional (but recommended) IDE such as Eclipse or RAD 8.0.3, WebSphere Application Server V8 contains everything you need to get started - the Java SDK (depending on platform), the embeddable container, JPA client package, and the Derby database. When you install the beta be sure to install Stand-alone thin clients, resource adapters and embeddable containers.
For the purposes of an example, let's say we have a simple EJB module that is used to manage personal contacts. The ContactModule contains a simple annotated session bean named ContactBean which is primarly a transactional facade over JPA operations. The operations provided by the EJB allow you to find a contact by name, save a contact, and clear all contacts. The JPA operations operate on a persistent entity, Contact. The ContactBean is a session bean that implements a local interface ContactLocal. While EJB 3.1 will now provide access to an bean without a Local interface, using interfaces is still a good practice. Interfaces provide a layer of abstraction from the base bean. They allow the bean to implement multiple specialized interfaces or allow the client to call a different EJB which implements the interface without modifying Java code. Here are our EJB and JPA classes and persistence.xml:
ejbs/ContactBean.java
package ejbs;
import static javax.ejb.TransactionAttributeType.REQUIRED;
import static javax.ejb.TransactionAttributeType.SUPPORTS;
import java.util.List;
import javax.annotation.Resource;
import javax.annotation.Resources;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.sql.DataSource;
import entities.Contact;
// Data source references for JPA
@Resources({
@Resource(name="jdbc/ContactDS", type=DataSource.class),
@Resource(name="jdbc/ContactNoTxDS", type=DataSource.class)
})
@Stateless
public class ContactBean implements ContactLocal {
@PersistenceContext(unitName="ContactPU")
private EntityManager em;
@TransactionAttribute(SUPPORTS)
public List<Contact> findContactsByLastName(String lastName) {
TypedQuery<Contact> q = em.createNamedQuery("ByLastName", Contact.class);
q.setParameter("lastName", lastName);
return q.getResultList();
}
@TransactionAttribute(REQUIRED)
public Contact saveContact(Contact c) {
return em.merge(c);
}
@TransactionAttribute(REQUIRED)
public void clearContacts() {
Query q = em.createNamedQuery("DeleteAll");
q.executeUpdate();
}
}
ejbs/ContactLocal.java
package ejbs;
import java.util.List;
import javax.ejb.Local;
import entities.Contact;
@Local
public interface ContactLocal {
public List<Contact> findContactsByLastName(String lastName);
public Contact saveContact(Contact c);
public void clearContacts();
}
entities/Contact.java
package entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
@Entity
@NamedQueries( {
@NamedQuery(name="ByLastName",
query="SELECT c FROM Contact c WHERE c.lastName = :lastName"),
@NamedQuery(name="DeleteAll", query="DELETE FROM Contact c")
})
public class Contact {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String phoneNumber;
private String emailAddress;
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
}
META-INF/persistence.xml
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="ContactPU" transaction-type="JTA">
<jta-data-source>java:comp/env/jdbc/ContactDS</jta-data-source>
<non-jta-data-source>java:comp/env/jdbc/ContactNoTxDS</non-jta-data-source>
<class>entities.Contact</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<!-- Create the database tables on startup -->
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
</properties>
</persistence-unit>
</persistence>
The simple EJB + JPA module looks like this when loaded as an Eclipse project:
Since ContactModule uses only Java EE APIs, it only requires WAS_HOME\dev\JavaEE\j2ee.jar to compile. If you use WebSphere, OpenJPA, or other extensions you'll need to include additional jars in your build path. Remember, this is only to compile the EJB + JPA module. We'll need different jars to actually run the module in the Embeddable EJB Container.
Now that the module is defined, we need to create an EJB jar file. As of EJB 3.0, a deployment descriptor (ejb-jar.xml) is not required. You can either use the "jar" command to create the jar or use the Eclipse Export... -> Java -> JAR file wizard. Export the jar to a location such as C:\ContactModule.jar. The EJB jar file should contain:
- META-INF/MANIFEST.MF
- entities/Contact.class
- ejbs/ContactBean.class
- ejbs/ContactLocal.class
- META-INF/persistence.xml
Let's take a look at how the Embeddable EJB Container can be used to simplify this testing using the popular JUnit 4 test framework. Again, I used an Eclipse project, but provided that you have the necessary JUnit 4 libraries, you can also run the same tests from the command line. To test the basic functions of our ContactBean session bean, we'll create a simple JUnit 4 annotated test class, TestContactBean.
test/TestContactBean.java
package test;
import java.util.List;
import javax.ejb.embeddable.EJBContainer;
import org.junit.Assert;
import org.junit.Test;
import ejbs.ContactLocal;
import entities.Contact;
public class TestContactBean {
@Test
public void testContactBean() {
// Create test data
Contact c1 = new Contact();
c1.setFirstName("John");
c1.setLastName("Smith");
c1.setPhoneNumber("555-555-5555");
c1.setEmailAddress("john@someplace.org");
Contact c2 = new Contact();
c2.setFirstName("Jane");
c2.setLastName("Smith");
c2.setPhoneNumber("555-555-5555");
c2.setEmailAddress("jane@someplace.org");
// Create the embeddable container
EJBContainer ec = EJBContainer.createEJBContainer();
try {
// Use the embeddable container context to look up a bean
ContactLocal contactBean = (ContactLocal) ec.getContext().lookup(
"java:global/ContactModule/ContactBean!ejbs.ContactLocal");
// Clear existing contacts
contactBean.clearContacts();
// Save new contacts
contactBean.saveContact(c1);
contactBean.saveContact(c2);
// Query contacts and verify results
List contacts = contactBean.findContactsByLastName("Smith");
Assert.assertEquals(2, contacts.size());
for (Contact c : contacts) {
Assert.assertTrue(c.getLastName().equals("Smith"));
}
} catch (Throwable t) {
Assert.fail("Caught unexpected exception: " + t.getMessage());
} finally {
ec.close();
}
}
}
If you've used JPA in JSE-mode, the pattern looks pretty familiar, right? We use a static EJBContainer.createEJBContainer() method to create the embeddable container and then use a lookup to get an instance of the bean. Knowing what JNDI name to use can be a little tricky, but the new java:global namespace simplifies that as well. In general, you can use java:global/
If we were simply testing EJBs we'd be all set. JPA has a few more requirements we need to take care of. First, since we'll be using Derby as our database, we need to add the Derby library to our classpath. It can be found at WAS_HOME\derby\lib\derby.jar. Second, in an EE environment, JPA uses data sources to connect to a relational database. JPA typically requires both a transactional and non-transactional data source. WebSphere's embeddable container allows us to very easily configure data sources one of two ways. The most flexible of the two approaches is to configure them within a properties file. If a file named embeddable.properties is found on the classpath the properties in this file will automatically get loaded by the embeddable container . The WebSphere Embeddable EJB Container also allows you to pass properties in via Map on the createEJBContainer method. As a third option you can specify a system property to inform the embeddable container where its properties file resides. More information on the properties supported by the embeddable container can be found in the Information Center article Embeddable EJB container configuration properties. You'll find that the embeddable container is extremely configurable. You can even configure settings such as cache size, security options, and logging.
embeddable.properties
# JPA Transactional data source definition
DataSource.ds1.name=jdbc/ContactDS
DataSource.ds1.className=org.apache.derby.jdbc.EmbeddedXADataSource
DataSource.ds1.createDatabase=create
DataSource.ds1.databaseName=ContactDB
DataSource.ds1.transactional=true
# JPA non-Transactional data source definition
DataSource.ds2.name=jdbc/ContactNoTxDS
DataSource.ds2.className=org.apache.derby.jdbc.EmbeddedXADataSource
DataSource.ds2.createDatabase=create
DataSource.ds2.databaseName=ContactDB
DataSource.ds2.transactional=false
# Definitions for global to component namespace bindings - required for @Resource defs
Bean.#ContactModule#ContactBean.ResourceRef.BindingName.jdbc/ContactDS=jdbc/ContactDS
Bean.#ContactModule#ContactBean.ResourceRef.BindingName.jdbc/ContactNoTxDS=jdbc/ContactNoTxDS
Another approach is to define the data source using one or more @DataSourceDefinition(s) annotation(s) on the EJB. For example:
@DataSourceDefinitions({
@DataSourceDefinition(name="jdbc/ContactDS",
className="org.apache.derby.jdbc.EmbeddedXADataSource",
databaseName="JPAinEmbeddedContainer",
transactional=true,
properties={"createDatabase=create"}),
@DataSourceDefinition(name="jdbc/ContactNoTxDS",
className="org.apache.derby.jdbc.EmbeddedXADataSource",
databaseName="JPAinEmbeddedContainer",
transactional=false,
properties={"createDatabase=create"})
})
@Stateless
public class ContactBean implements ContactLocal {
...
}
As you can see, using @DataSourceDefinition is a very simple way to define data sources. There is no properties file or @Resource references to manage. The data sources automatically get registered into the java:comp/env namespace (take a look jta-data-source and non-jta-data-source definitions in the persistence.xml - as a best practice, component data sources are used). However, simplicity comes at the cost of flexibility. A change in the data source definition requires recompiling the application.
There's one more JPA-specific item we need to take care of before running the test. In the full application server, the container takes care of JPA entity enhancement. This does not occur in the embeddable container. Fortunately, you can either enhance the entities at build time or the JPA agent-based enhancer can be used to perform the enhancement at runtime. Enabling the enhancer is as simple as specifying the JVM option -javaagent:C:\/"Program Files/"\IBM\WebSphere\AppServer\runtimes\com.ibm.ws.jpa.thinclient_8.0.0.jar on the java or Eclipse jUnit invocation. If all goes well, you should see a result similar to this...
More information about the Embeddable EJB Container is available in the WebSphere Application Server V8 Information Center. Happy testing!
-Jeremy