This chapter covers
2.2 Starting a Java Persistence project
2.2.3 Introducing EJB components
Java Persistence starts to shine when you also work with EJB 3.0 session beans and message-driven beans (and other Java EE 5.0 standards). The EJB 3.0 specification has been designed to permit the integration of persistence, so you can, for exam-ple, get automatic transaction demarcation on bean method boundaries, or a per-sistence context (think Session) that spans the lifecycle of a stateful session EJB.
This section will get you started with EJB 3.0 and JPA in a managed Java EE environment; you’ll again modify the “Hello World” application to learn the basics. You need a Java EE environment first—a runtime container that provides Java EE services. There are two ways you can get it:
■ You can install a full Java EE 5.0 application server that supports EJB 3.0 and JPA. Several open source (Sun GlassFish, JBoss AS, ObjectWeb EasyBeans) and other proprietary licensed alternatives are on the market at the time of writing, and probably more will be available when you read this book.
■ You can install a modular server that provides only the services you need, selected from the full Java EE 5.0 bundle. At a minimum, you probably want an EJB 3.0 container, JTA transaction services, and a JNDI registry. At the time of writing, only JBoss AS provided modular Java EE 5.0 services in an easily customizable package.
To keep things simple and to show you how easy it is to get started with EJB 3.0, you’ll install and configure the modular JBoss Application Server and enable only the Java EE 5.0 services you need.
Installing the EJB container
Go to http://jboss.com/products/ejb3, download the modular embeddable server, and unzip the downloaded archive. Copy all libraries that come with the server into your project’s WORKDIR/lib directory, and copy all included configu-ration files to your WORKDIR/src directory. You should now have the following directory layout:
The JBoss embeddable server relies on Hibernate for Java Persistence, so the default.persistence.properties file contains default settings for Hibernate that are needed for all deployments (such as JTA integration settings). The ejb3-intercep-tors-aop.xml and embedded-jboss-beans.xml configuration files contain the ser-vices configuration of the server—you can look at these files, but you don’t need to modify them now. By default, at the time of writing, the enabled services are JNDI, JCA, JTA, and the EJB 3.0 container—exactly what you need.
To migrate the “Hello World” application, you need a managed datasource, which is a database connection that is handled by the embeddable server. The eas-iest way to configure a managed datasource is to add a configuration file that deploys the datasource as a managed service. Create the file in listing 2.14 as WORKDIR/etc/META-INF/helloworld-beans.xml.
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
xmlns="urn:jboss:bean-deployer:2.0">
<!-- Enable a JCA datasource available through JNDI -->
<bean name="helloWorldDatasourceFactory"
Listing 2.14 Datasource configuration file for the JBoss server
class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
<property name="jndiName">java:/HelloWorldDS</property>
<!-- HSQLDB -->
<property name="driverClass">
org.hsqldb.jdbcDriver </property>
<property name="connectionURL">
jdbc:hsqldb:hsql://localhost </property>
<property name="userName">sa</property>
<property name="minSize">0</property>
<property name="maxSize">10</property>
<property name="blockingTimeout">1000</property>
<property name="idleTimeout">100000</property>
<property name="transactionManager">
<inject bean="TransactionManager"/>
</property>
<property name="cachedConnectionManager">
<inject bean="CachedConnectionManager"/>
</property>
<property name="initialContextProperties">
<inject bean="InitialContextProperties"/>
</property>
</bean>
<bean name="HelloWorldDS" class="java.lang.Object">
<constructor factoryMethod="getDatasource">
<factory bean="helloWorldDatasourceFactory"/>
</constructor>
</bean>
</deployment>
Again, the XML header and schema declaration aren’t important for this exam-ple. You set up two beans: The first is a factory that can produce the second type of bean. The LocalTxDataSource is effectively now your database connection pool, and all your connection pool settings are available on this factory. The fac-tory binds a managed datasource under the JNDI name java:/HelloWorldDS.
The second bean configuration declares how the registered object named HelloWorldDS should be instantiated, if another service looks it up in the JNDI registry. Your “Hello World” application asks for the datasource under this name, and the server calls getDatasource() on the LocalTxDataSource factory to obtain it.
Also note that we added some line breaks in the property values to make this more readable—you shouldn’t do this in your real configuration file (unless your database username contains a line break).
Configuring the persistence unit
Next, you need to change the persistence unit configuration of the “Hello World”
application to access a managed JTA datasource, instead of a resource-local connec-tion pool. Change your WORKDIR/etc/META-INF/persistence.xml file as follows:
<persistence ...>
<persistence-unit name="helloworld">
<jta-data-source>java:/HelloWorldDS</jta-data-source>
<properties>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
You removed many Hibernate configuration options that are no longer relevant, such as the connection pool and database connection settings. Instead, you set a
<jta-data-source> property with the name of the datasource as bound in JNDI. Don’t forget that you still need to configure the correct SQL dialect and any other Hibernate options that aren’t present in default.persistence.properties.
The installation and configuration of the environment is now complete, (we’ll show you the purpose of the jndi.properties files in a moment) and you can rewrite the application code with EJBs.
Writing EJBs
There are many ways to design and create an application with managed compo-nents. The “Hello World” application isn’t sophisticated enough to show elabo-rate examples, so we’ll introduce only the most basic type of EJB, a stateless session bean. (You’ve already seen entity classes—annotated plain Java classes that can have persistent instances. Note that the term entity bean only refers to the old EJB 2.1 entity beans; EJB 3.0 and Java Persistence standardize a lightweight program-ming model for plain entity classes.)
Every EJB session bean needs a business interface. This isn’t a special interface that needs to implement predefined methods or extend existing ones; it’s plain Java. Create the following interface in the WORKDIR/src/hello package:
package hello;
public interface MessageHandler { public void saveMessages();
public void showMessages();
}
A MessageHandler can save and show messages; it’s straightforward. The actual EJB implements this business interface, which is by default considered a local interface (that is, remote EJB clients cannot call it); see listing 2.15.
package hello;
import javax.ejb.Stateless;
import javax.persistence.*;
import java.util.List;
@Stateless
public class MessageHandlerBean implements MessageHandler { @PersistenceContext
EntityManager em;
public void saveMessages() {
Message message = new Message("Hello World");
em.persist(message);
}
public void showMessages() { List messages =
em.createQuery("select m from Message m ➥ order by m.text asc")
.getResultList();
System.out.println(messages.size() + " message(s) found:");
for (Object m : messages) {
Message loadedMsg = (Message) m;
System.out.println(loadedMsg.getText());
} } }
Listing 2.15 The “Hello World” EJB session bean application code
There are several interesting things to observe in this implementation. First, it’s a plain Java class with no hard dependencies on any other package. It becomes an EJB only with a single metadata annotation, @Stateless. EJBs support container-managed services, so you can apply the @PersistenceContext annotation, and the server injects a fresh EntityManager instance whenever a method on this stateless bean is called. Each method is also assigned a transaction automatically by the container. The transaction starts when the method is called, and commits when the method returns. (It would be rolled back when an exception is thrown inside the method.)
You can now modify the HelloWorld main class and delegate all the work of storing and showing messages to the MessageHandler.
Running the application
The main class of the “Hello World” application calls the MessageHandler state-less session bean after looking it up in the JNDI registry. Obviously, the managed environment and the whole application server, including the JNDI registry, must be booted first. You do all of this in the main() method of HelloWorld.java (see listing 2.16).
package hello;
import org.jboss.ejb3.embedded.EJB3StandaloneBootstrap;
import javax.naming.InitialContext;
public class HelloWorld {
public static void main(String[] args) throws Exception {
// Boot the JBoss Microcontainer with EJB3 settings, automatically // loads ejb3-interceptors-aop.xml and embedded-jboss-beans.xml EJB3StandaloneBootstrap.boot(null);
// Deploy custom stateless beans (datasource, mostly) EJB3StandaloneBootstrap
.deployXmlResource("META-INF/helloworld-beans.xml");
// Deploy all EJBs found on classpath (slow, scans all) // EJB3StandaloneBootstrap.scanClasspath();
// Deploy all EJBs found on classpath (fast, scans build directory) // This is a relative location, matching the substring end of one // of java.class.path locations. Print out the value of
// System.getProperty("java.class.path") to see all paths.
EJB3StandaloneBootstrap.scanClasspath("helloworld-ejb3/bin");
// Create InitialContext from jndi.properties Listing 2.16 “Hello World” main application code, calling EJBs
InitialContext initialContext = new InitialContext();
// Look up the stateless MessageHandler EJB
MessageHandler msgHandler = (MessageHandler) initialContext .lookup("MessageHandlerBean/local");
// Call the stateless EJB msgHandler.saveMessages();
msgHandler.showMessages();
// Shut down EJB container
EJB3StandaloneBootstrap.shutdown();
} }
The first command in main() boots the server’s kernel and deploys the base ser-vices found in the service configuration files. Next, the datasource factory config-uration you created earlier in helloworld-beans.xml is deployed, and the datasource is bound to JNDI by the container. From that point on, the container is ready to deploy EJBs. The easiest (but often not the fastest) way to deploy all EJBs is to let the container search the whole classpath for any class that has an EJB annotation. To learn about the many other deployment options available, check the JBoss AS documentation bundled in the download.
To look up an EJB, you need an InitialContext, which is your entry point for the JNDI registry. If you instantiate an InitialContext, Java automatically looks for the file jndi.properties on your classpath. You need to create this file in WORKDIR/ etc with settings that match the JBoss server’s JNDI registry configuration:
java.naming.factory.initial
➥ org.jnp.interfaces.LocalOnlyContextFactory
java.naming.factory.url.pkgs org.jboss.naming:org.jnp.interfaces
You don’t need to know exactly what this configuration means, but it basically points your InitialContext to a JNDI registry running in the local virtual machine (remote EJB client calls would require a JNDI service that supports remote communication).
By default, you look up the MessageHandler bean by the name of an imple-mentation class, with the /local suffix for a local interface. How EJBs are named, how they’re bound to JNDI, and how you look them up varies and can be custom-ized. These are the defaults for the JBoss server.
Finally, you call the MessageHandler EJB and let it do all the work automati-cally in two units—each method call will result in a separate transaction.
This completes our first example with managed EJB components and inte-grated JPA. You can probably already see how automatic transaction demarcation and EntityManager injection can improve the readability of your code. Later, we’ll show you how stateful session beans can help you implement sophisticated conversations between the user and the application, with transactional semantics.
Furthermore, the EJB components don’t contain any unnecessary glue code or infrastructure methods, and they’re fully reusable, portable, and executable in any EJB 3.0 container.
NOTE Packaging of persistence units —We didn’t talk much about the packaging of persistence units—you didn’t need to package the “Hello World”
example for any of the deployments. However, if you want to use features such as hot redeployment on a full application server, you need to pack-age your application correctly. This includes the usual combination of JARs, WARs, EJB-JARs, and EARs. Deployment and packaging is often also vendor-specific, so you should consult the documentation of your appli-cation server for more information. JPA persistence units can be scoped to JARs, WARs, and EJB-JARs, which means that one or several of these archives contains all the annotated classes and a META-INF /persis-tence.xml configuration file with all settings for this particular unit. You can wrap one or several JARs, WARs, and EJB-JARs in a single enterprise application archive, an EAR. Your application server should correctly detect all persistence units and create the necessary factories automati-cally. With a unit name attribute on the @PersistenceContext annota-tion, you instruct the container to inject an EntityManager from a particular unit.
Full portability of an application isn’t often a primary reason to use JPA or EJB 3.0.
After all, you made a decision to use Hibernate as your JPA persistence provider.
Let’s look at how you can fall back and use a Hibernate native feature from time to time.