2.2. Starting a Java Persistence project

In the following sections, we show you some of the advantages of JPA and the new EJB 3.0 standard, and how annotations and the standardized programming interfaces can simplify application development, even when compared with Hibernate. Obviously, designing and linking to standardized interfaces is an advantage if you ever need to port or deploy an application on a different runtime environment. Besides portability, though, there are many good reasons to give JPA a closer look.

We'll now guide you through another "Hello World" example, this time with Hibernate Annotations and Hibernate EntityManager. You'll reuse the basic project infrastructure introduced in the previous section so you can see where JPA differs from Hibernate. After working with annotations and the JPA interfaces, we'll show how an application integrates and interacts with other managed components—EJBs. We'll discuss many more application design examples later in the book; however, this first glimpse will let you decide on a particular approach as soon as possible.

2.2.1. Using Hibernate Annotations

Let's first use Hibernate Annotations to replace the Hibernate XML mapping files with inline metadata. You may want to copy your existing "Hello World" project directory before you make the following changes—you'll migrate from native Hibernate to standard JPA mappings (and program code later on).

Copy the Hibernate Annotations libraries to your WORKDIR/lib directory—see the Hibernate Annotations documentation for a list of required libraries. (At the time of writing, hibernate-annotations.jar and the API stubs in ejb3-persistence.jar were required.)

Now delete the src/hello/Message.hbm.xml file. You'll replace this file with annotations in the src/hello/Message.java class source, as shown in listing 2.10.

Listing 2-10. Mapping the Message class with annotations
package hello;

import javax.persistence.*;

@Entity
@Table(name = "MESSAGES")
public class Message {

    @Id @GeneratedValue
    @Column(name = "MESSAGE_ID")
    private Long id;

    @Column(name = "MESSAGE_TEXT")
    private String text;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "NEXT_MESSAGE_ID")
    private Message nextMessage;

    private Message() {}

    public Message(String text) {
        this.text = text;
    }

    public Long getId() {
        return id;
    }
    private void setId(Long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }

     public Message getNextMessage() {
         return nextMessage;
     }
     public void setNextMessage(Message nextMessage) {
         this.nextMessage = nextMessage;
     }
}

The first thing you'll probably notice in this updated business class is the import of the javax.persistence interfaces. Inside this package are all the standardized JPA annotations you need to map the @Entity class to a database @Table. You put annotations on the private fields of the class, starting with @Id and @GeneratedValue for the database identifier mapping. The JPA persistence provider detects that the @Id annotation is on a field and assumes that it should access properties on an object directly through fields at runtime. If you placed the @Id annotation on the getId() method, you'd enable access to properties through getter and setter methods by default. Hence, all other annotations are also placed on either fields or getter methods, following the selected strategy.

Note that the @Table, @Column, and @JoinColumn annotations aren't necessary. All properties of an entity are automatically considered persistent, with default strategies and table/column names. You add them here for clarity and to get the same results as with the XML mapping file. Compare the two mapping metadata strategies now, and you'll see that annotations are much more convenient and reduce the lines of metadata significantly. Annotations are also type-safe, they support autocompletion in your IDE as you type (like any other Java interfaces), and they make refactoring of classes and properties easier.

If you're worried that the import of the JPA interfaces will bind your code to this package, you should know that it's only required on your classpath when the annotations are used by Hibernate at runtime. You can load and execute this class without the JPA interfaces on your classpath as long as you don't want to load and store instances with Hibernate.

A second concern that developers new to annotations sometimes have relates to the inclusion of configuration metadata in Java source code. By definition, configuration metadata is metadata that can change for each deployment of the application, such as table names. JPA has a simple solution: You can override or replace all annotated metadata with XML metadata files. Later in the book, we'll show you how this is done.

Let's assume that this is all you want from JPA—annotations instead of XML. You don't want to use the JPA programming interfaces or query language; you'll use Hibernate Session and HQL. The only other change you need to make to your project, besides deleting the now obsolete XML mapping file, is a change in the Hibernate configuration, in hibernate.cfg.xml:

<!DOCTYPE hibernate-configuration SYSTEM
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
    <!-- ... Many property settings ... -->

    <!-- List of annotated classes-->
    <mapping class="hello.Message"/>

</session-factory>
</hibernate-configuration>

The Hibernate configuration file previously had a list of all XML mapping files. This has been replaced with a list of all annotated classes. If you use programmatic configuration of a SessionFactory, the addAnnotatedClass() method replaces the addResource() method:

// Load settings from hibernate.properties
AnnotationConfiguration cfg = new AnnotationConfiguration();
// ... set other configuration options programmatically

cfg.addAnnotatedClass(hello.Message.class);

SessionFactory sessionFactory = cfg.buildSessionFactory();

Note that you have now used AnnotationConfiguration instead of the basic Hibernate Configuration interface—this extension understands annotated classes. At a minimum, you also need to change your initializer in HibernateUtil to use that interface. If you export the database schema with an Ant target, replace <configuration> with <annotationconfiguration> in your build.xml file.

This is all you need to change to run the "Hello World" application with annotations. Try running it again, probably with a fresh database.

Annotation metadata can also be global, although you don't need this for the "Hello World" application. Global annotation metadata is placed in a file named package-info.java in a particular package directory. In addition to listing annotated classes, you need to add the packages that contain global metadata to your configuration. For example, in a Hibernate XML configuration file, you need to add the following:

<!DOCTYPE hibernate-configuration SYSTEM
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
    <!-- ... Many property settings ... -->

    <!-- List of annotated classes-->
    <mapping class="hello.Message"/>

    <!-- List of packages with package-info.java -->
    <mapping package="hello"/>

</session-factory>
</hibernate-configuration>

Or you could achieve the same results with programmatic configuration:

// Load settings from hibernate.properties
AnnotationConfiguration cfg = new AnnotationConfiguration();
// ... set other configuration options programmatically

cfg.addClass(hello.Message.class);

cfg.addPackage("hello");

SessionFactory sessionFactory = cfg.buildSessionFactory();

Let's take this one step further and replace the native Hibernate code that loads and stores messages with code that uses JPA. With Hibernate Annotations and Hibernate EntityManager, you can create portable and standards-compliant mappings and data access code.

2.2.2. Using Hibernate EntityManager

Hibernate EntityManager is a wrapper around Hibernate Core that provides the JPA programming interfaces, supports the JPA entity instance lifecycle, and allows you to write queries with the standardized Java Persistence query language. Because JPA functionality is a subset of Hibernate's native capabilities, you may wonder why you should use the EntityManager package on top of Hibernate. We'll present a list of advantages later in this section, but you'll see one particular simplification as soon as you configure your project for Hibernate EntityManager: You no longer have to list all annotated classes (or XML mapping files) in your configuration file.

Let's modify the "Hello World" project and prepare it for full JPA compatibility.

Basic JPA configuration

A SessionFactory represents a particular logical data-store configuration in a Hibernate application. The EntityManagerFactory has the same role in a JPA application, and you configure an EntityManagerFactory (EMF) either with configuration files or in application code just as you would configure a SessionFactory. The configuration of an EMF, together with a set of mapping metadata (usually annotated classes), is called the persistence unit.

The notion of a persistence unit also includes the packaging of the application, but we want to keep this as simple as possible for "Hello World"; we'll assume that you want to start with a standardized JPA configuration and no special packaging. Not only the content, but also the name and location of the JPA configuration file for a persistence unit are standardized.

Create a directory named WORKDIR/etc/META-INF and place the basic configuration file named persistence.xml, shown in listing 2.11, in that directory:

Listing 2-11. Persistence unit configuration file
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
   version="1.0">

   <persistence-unit name="helloworld">
      <properties>
          <property name="hibernate.ejb.cfgfile"
              value="/hibernate.cfg.xml"/>
      </properties>
   </persistence-unit>

</persistence>

Every persistence unit needs a name, and in this case it's helloworld.


Note:

The XML header in the preceding persistence unit configuration file declares what schema should be used, and it's always the same. We'll omit it in future examples and assume that you'll add it.


A persistence unit is further configured with an arbitrary number of properties, which are all vendor-specific. The property in the previous example, hibernate.ejb.cfgfile, acts as a catchall. It refers to a hibernate.cfg.xml file (in the root of the classpath) that contains all settings for this persistence unit—you're reusing the existing Hibernate configuration. Later, you'll move all configuration details into the persistence.xml file, but for now you're more interested in running "Hello World" with JPA.

The JPA standard says that the persistence.xml file needs to be present in the META-INF directory of a deployed persistence unit. Because you aren't really packaging and deploying the persistence unit, this means that you have to copy persistence.xml into a META-INF directory of the build output directory. Modify your build.xml, and add the following to the copymetafiles target:

<property name="src.etc.dir" value="etc"/>

<target name="copymetafiles">

<!-- Copy metadata to build -->
    <copy todir="${build.dir}">
      <fileset dir="${src.java.dir}">
         <patternset refid="meta.files"/>
      </fileset>
    </copy>

    <!-- Copy configuration files from etc/ -->
    <copy todir="${build.dir}">
      <fileset dir="${src.etc.dir}">
         <patternset refid="meta.files"/>
      </fileset>
    </copy>

</target>

Everything found in WORKDIR/etc that matches the meta.files pattern is copied to the build output directory, which is part of the classpath at runtime.

Let's rewrite the main application code with JPA.

"Hello World" with JPA

These are your primary programming interfaces in Java Persistence:

  • javax.persistence.Persistence—A startup class that provides a static method for the creation of an EntityManagerFactory.

  • javax.persistence.EntityManagerFactory—The equivalent to a Hibernate SessionFactory. This runtime object represents a particular persistence unit. It's thread-safe, is usually handled as a singleton, and provides methods for the creation of EntityManager instances.

  • javax.persistence.EntityManager—The equivalent to a Hibernate Session. This single-threaded, nonshared object represents a particular unit of work for data access. It provides methods to manage the lifecycle of entity instances and to create Query instances.

  • javax.persistence.Query—This is the equivalent to a Hibernate Query. An object is a particular JPA query language or native SQL query representation, and it allows safe binding of parameters and provides various methods for the execution of the query.

  • javax.persistence.EntityTransaction—This is the equivalent to a Hibernate Transaction, used in Java SE environments for the demarcation of RESOURCE_LOCAL transactions. In Java EE, you rely on the standardized javax.transaction.UserTransaction interface of JTA for programmatic transaction demarcation.

To use the JPA interfaces, you need to copy the required libraries to your WORKDIR/lib directory; check the documentation bundled with Hibernate EntityManager for an up-to-date list. You can then rewrite the code in WORKDIR/src/hello/HelloWorld.java and switch from Hibernate to JPA interfaces (see listing 2.12).

Listing 2-12. The "Hello World" main application code with JPA
package hello;

import java.util.*;
import javax.persistence.*;

public class HelloWorld {

    public static void main(String[] args) {

        // Start EntityManagerFactory
        EntityManagerFactory emf =
               Persistence.createEntityManagerFactory("helloworld");

        // First unit of work
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        Message message = new Message("Hello World");
        em.persist(message);

        tx.commit();
        em.close();

        // Second unit of work
        EntityManager newEm = emf.createEntityManager();
        EntityTransaction newTx = newEm.getTransaction();
        newTx.begin();

        List messages = newEm
            .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()); } newTx.commit(); newEm.close(); // Shutting down the application emf.close(); } }

The first thing you probably notice in this code is that there is no Hibernate import anymore, only javax.peristence.*. The EntityManagerFactory is created with a static call to Persistence and the name of the persistence unit. The rest of the code should be self-explanatory—you use JPA just like Hibernate, though there are some minor differences in the API, and methods have slightly different names. Furthermore, you didn't use the HibernateUtil class for static initialization of the infrastructure; you can write a JPAUtil class and move the creation of an EntityManagerFactory there if you want, or you can remove the now unused WORKDIR/src/persistence package.

JPA also supports programmatic configuration, with a map of options:

Map myProperties = new HashMap();
myProperties.put("hibernate.hbm2ddl.auto", "create-drop");
EntityManagerFactory emf =
 Persistence.createEntityManagerFactory("helloworld", myProperties);

Custom programmatic properties override any property you've set in the persistence.xml configuration file.

Try to run the ported HelloWorld code with a fresh database. You should see the exact same log output on your screen as you did with native Hibernate—the JPA persistence provider engine is Hibernate.

Automatic detection of metadata

We promised earlier that you won't have to list all your annotated classes or XML mapping files in the configuration, but it's still there, in hibernate.cfg.xml. Let's enable the autodetection feature of JPA.

Run the "Hello World" application again after switching to DEBUG logging for the org.hibernate package. Some additional lines should appear in your log:

...
Ejb3Configuration:141
    - Trying to find persistence unit: helloworld
Ejb3Configuration:150
    - Analyse of persistence.xml:
        file:/helloworld/build/META-INF/persistence.xml
PersistenceXmlLoader:115
    - Persistent Unit name from persistence.xml: helloworld
Ejb3Configuration:359
    - Detect class: true; detect hbm: true
JarVisitor:178
    - Searching mapped entities in jar/par: file:/helloworld/build
JarVisitor:217
    - Filtering: hello.HelloWorld
JarVisitor:217
    - Filtering: hello.Message

JarVisitor:255
    - Java element filter matched for hello.Message
Ejb3Configuration:101
    - Creating Factory: helloworld
...

On startup, the Persistence.createEntityManagerFactory() method tries to locate the persistence unit named helloworld. It searches the classpath for all META-INF/persistence.xml files and then configures the EMF if a match is found. The second part of the log shows something you probably didn't expect. The JPA persistence provider tried to find all annotated classes and all Hibernate XML mapping files in the build output directory. The list of annotated classes (or the list of XML mapping files) in hibernate.cfg.xml isn't needed, because hello.Message, the annotated entity class, has already been found.

Instead of removing only this single unnecessary option from hibernate.cfg.xml, let's remove the whole file and move all configuration details into persistence.xml (see listing 2.13).

Listing 2-13. Full persistence unit configuration file
<persistence-unit name="helloworld">

   <provider>org.hibernate.ejb.HibernatePersistence</provider>

   <!-- Not needed, Hibernate supports auto-detection in JSE
       <class>hello.Message</class>
    -->

  <properties>
      <property name="hibernate.archive.autodetection"
          value="class, hbm"/>

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

      <property name="hibernate.connection.driver_class"
                value="org.hsqldb.jdbcDriver"/>
      <property name="hibernate.connection.url"
                value="jdbc:hsqldb:hsql://localhost"/>
      <property name="hibernate.connection.username"
                value="sa"/>

      <property name="hibernate.c3p0.min_size"
                value="5"/>
      <property name="hibernate.c3p0.max_size"
                value="20"/>
      <property name="hibernate.c3p0.timeout"
                value="300"/>
      <property name="hibernate.c3p0.max_statements"
                value="50"/>

      <property name="hibernate.c3p0.idle_test_period"
                value="3000"/>

      <property name="hibernate.dialect"
                value="org.hibernate.dialect.HSQLDialect"/>

      <property name="hibernate.hbm2ddl.auto" value="create"/>

  </properties>
</persistence-unit>

There are three interesting new elements in this configuration file. First, you set an explicit <provider> that should be used for this persistence unit. This is usually required only if you work with several JPA implementations at the same time, but we hope that Hibernate will, of course, be the only one. Next, the specification requires that you list all annotated classes with <class> elements if you deploy in a non-Java EE environment—Hibernate supports autodetection of mapping metadata everywhere, making this optional. Finally, the Hibernate configuration setting archive.autodetection tells Hibernate what metadata to scan for automatically: annotated classes (class) and/or Hibernate XML mapping files (hbm). By default, Hibernate EntityManager scans for both. The rest of the configuration file contains all options we explained and used earlier in this chapter in the regular hibernate.cfg.xml file.

Automatic detection of annotated classes and XML mapping files is a great feature of JPA. It's usually only available in a Java EE application server; at least, this is what the EJB 3.0 specification guarantees. But Hibernate, as a JPA provider, also implements it in plain Java SE, though you may not be able to use the exact same configuration with any other JPA provider.

You've now created an application that is fully JPA specification-compliant. Your project directory should look like this (note that we also moved log4j.properties to the etc/ directory):

WORKDIR
+etc
  log4j.properties
  +META-INF
   persistence.xml
+lib
  <all required libraries>
+src
  +hello
    HelloWorld.java
    Message.java

All JPA configuration settings are bundled in persistence.xml, all mapping metadata is included in the Java source code of the Message class, and Hibernate automatically scans and finds the metadata on startup. Compared to pure Hibernate, you now have these benefits:

  • Automatic scanning of deployed metadata, an important feature in large projects. Maintaining a list of annotated classes or mapping files becomes difficult if hundreds of entities are developed by a large team.

  • Standardized and simplified configuration, with a standard location for the configuration file, and a deployment concept—the persistence unit—that has many more advantages in larger projects that wrap several units (JARs) in an application archive (EAR).

  • Standardized data access code, entity instance lifecycle, and queries that are fully portable. There is no proprietary import in your application.

These are only some of the advantages of JPA. You'll see its real power if you combine it with the full EJB 3.0 programming model and other managed components.

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 example, get automatic transaction demarcation on bean method boundaries, or a persistence 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 configuration files to your WORKDIR/src directory. You should now have the following directory layout:

WORKDIR
+etc
  default.persistence.properties
  ejb3-interceptors-aop.xml
  embedded-jboss-beans.xml
  jndi.properties
  log4j.properties
  +META-INF
    helloworld-beans.xml
    persistence.xml
+lib
  <all required libraries>
+src
  +hello
    HelloWorld.java
    Message.java

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-interceptors-aop.xml and embedded-jboss-beans.xml configuration files contain the services 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 easiest 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.

Listing 2-14. Datasource configuration file for the JBoss server
<?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"

   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 example. 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 factory 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 connection 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 components. The "Hello World" application isn't sophisticated enough to show elaborate 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 programming 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.

Listing 2-15. The "Hello World" EJB session bean application code
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()); } } }

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 stateless 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).

Listing 2-16. "Hello World" main application code, calling EJBs
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

    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 services found in the service configuration files. Next, the datasource factory configuration 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 implementation 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 customized. These are the defaults for the JBoss server.

Finally, you call the MessageHandler EJB and let it do all the work automatically in two units—each method call will result in a separate transaction.

This completes our first example with managed EJB components and integrated 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 package 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 application 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/persistence.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 automatically. With a unit name attribute on the @PersistenceContext annotation, 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.

2.2.4. Switching to Hibernate interfaces

You decided to use Hibernate as a JPA persistence provider for several reasons: First, Hibernate is a good JPA implementation that provides many options that don't affect your code. For example, you can enable the Hibernate second-level data cache in your JPA configuration, and transparently improve the performance and scalability of your application without touching any code.

Second, you can use native Hibernate mappings or APIs when needed. We discuss the mixing of mappings (especially annotations) in chapter 3, section 3.3, "Object/relational mapping metadata," but here we want to show how you can use a Hibernate API in your JPA application, when needed. Obviously, importing a Hibernate API into your code makes porting the code to a different JPA provider more difficult. Hence, it becomes critically important to isolate these parts of your code properly, or at least to document why and when you used a native Hibernate feature.

You can fall back to Hibernate APIs from their equivalent JPA interfaces and get, for example, a Configuration, a SessionFactory, and even a Session whenever needed.

For example, instead of creating an EntityManagerFactory with the Persistence static class, you can use a Hibernate Ejb3Configuration:

Ejb3Configuration cfg = new Ejb3Configuration();
EntityManagerFactory emf =
  cfg.configure("/custom/hibernate.cfg.xml")
     .setProperty("hibernate.show_sql", "false")
     .setInterceptor( new MyInterceptor() )
     .addAnnotatedClass( hello.Message.class )
     .addResource( "/Foo.hbm.xml")
     .buildEntityManagerFactory();

AnnotationConfiguration
        hibCfg = cfg.getHibernateConfiguration();

The Ejb3Configuration is a new interface that duplicates the regular Hibernate Configuration instead of extending it (this is an implementation detail). This means you can get a plain AnnotationConfiguration object from an Ejb3Configuration, for example, and pass it to a SchemaExport instance programmatically.

The SessionFactory interface is useful if you need programmatic control over the second-level cache regions. You can get a SessionFactory by casting the EntityManagerFactory first:

HibernateEntityManagerFactory hibEMF =
        (HibernateEntityManagerFactory) emf;
SessionFactory sf = hibEMF.getSessionFactory();

The same technique can be applied to get a Session from an EntityManager:

HibernateEntityManager hibEM =
    (HibernateEntityManager) em;
Session session = hibEM.getSession();

This isn't the only way to get a native API from the standardized EntityManager. The JPA specification supports a getDelegate() method that returns the underlying implementation:

Session session = (Session) entityManager.getDelegate();

Or you can get a Session injected into an EJB component (although this only works in the JBoss Application Server):

@Stateless
public class MessageHandlerBean implements MessageHandler {

    @PersistenceContext
    Session session;

    ...
}

In rare cases, you can fall back to plain JDBC interfaces from the Hibernate Session:

Connection jdbcConnection = session.connection();

This last option comes with some caveats: You aren't allowed to close the JDBC Connection you get from Hibernate—this happens automatically. The exception to this rule is that in an environment that relies on aggressive connection releases, which means in a JTA or CMT environment, you have to close the returned connection in application code.

A better and safer way to access a JDBC connection directly is through resource injection in a Java EE 5.0. Annotate a field or setter method in an EJB, an EJB listener, a servlet, a servlet filter, or even a JavaServer Faces backing bean, like this:

@Resource(mappedName="java:/HelloWorldDS") DataSource ds;

So far, we've assumed that you work on a new Hibernate or JPA project that involves no legacy application code or existing database schema. We now switch perspectives and consider a development process that is bottom-up. In such a scenario, you probably want to automatically reverse-engineer artifacts from an existing database schema.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.60.232