Persistence is a critical component of any enterprise application. And not surprisingly both Spring and EJB 3.0 provide robust support for it. True to its design philosophy, Spring doesn't re-implement a persistence framework but rather integrates with many popular ones including JDBC, Hibernate, JDO, iBatis, and (as of Spring 2.0) the Java Persistence API (JPA). With the EJB 3.0 specification entity beans have been replaced with the JPA.
JPA aims to provide a simplified, light-weight, object-relational mapping framework. This specification (a separate document within the EJB 3.0 specification) defines interfaces for interacting with the persistence provider and for mapping entities to a relational database.
To illustrate the comparability in support between Spring and EJB 3.0 consider the domain model for the flight booking application shown in Figure 1.
|Figure 1. Flight Booking Domain Model. The flight booking domains illustrated many interesting ORM concepts such as composition, bi-directionality, and ternary associations.|
The unit test below verifies that a ticket can be created, associated with existing flights, seats assigned, and saved to a database:
public void testSave()
Ticket ticket = TicketMother.create();
Flight outboundFlight = flightDAO.findById(1);
Flight returnFlight = flightDAO.findById(2);
new PassengerFlightDetails(getSeatForFlight(outboundFlight, "2A")));
new PassengerFlightDetails(getSeatForFlight(returnFlight, "2B")));
ticket = ticketDAO.findById(ticket.getId());
This test can be run successfully against both Spring and EJB 3.0 implementations indicating that the two technologies have functionally equivalent support for basic ORM persistence. The two implementations are also very similar. I have chosen to illustrate the Spring implementation with Hibernate since it is the most common ORM provider used in conjunction with Spring and because JPA support is not available in the current production release of Spring. A Spring/Hibernate implementation of the TicketDAO is class is illustrated below:
public class TicketSpringDAO extends HibernateDaoSupport implements TicketDAO
public Ticket save(Ticket ticket)
And the corresponding EJB 3.0 implementation is seen here:
public class TicketEJBDAO implements TicketDAO
@PersistenceContext(unitName = "flightdb")
private EntityManager em;
public Ticket save(Ticket ticket)
Obviously these implementations are both very similar. In the Spring implementation the details of working with Hibernate are abstracted away by the HibernateDaoSupport class, which provides the Hibernate plumbing and assists in working with the Spring Hibernate template and dependencies. In the EJB 3.0 implementation the entity manager is automatically injected by the container.
The Hibernate Session and the JPA Entity Manager are roughly equivalent but there are a couple of important differences to keep in mind. The Hibernate session is an entity cache as well an interface into the ORM engine. In JPA these two concepts are separated. The persistence context functions as the cache while the entity manager functions at the interface into the ORM engine.
It is also interesting to notice the different approaches towards mapping persistence entities to their relational equivalents. The Spring/Hibernate mapping is expressed in the following XML mapping file:
<hibernate-mapping package="org.jug.flight.domain" default-lazy="false">
<id name="id" column="id">
<generator class="native" />
<property name="status" type="Status" />
<many-to-one name="outboundFlight" column="outbound_flight" />
<many-to-one name="returnFlight" column="return_flight" />
<many-to-one name="passenger" column="passenger_id" cascade="all" />
JPA applications often choose to use annotations to express this mapping because it reduces the configuration required, keeps the mapping data close to the objects it modifies, and enhances visibility. This approach is illustrated below:
public class Ticket implements Serializable
private long id;
private Status status;
private double price;
@ManyToOne(cascade = CascadeType.PERSIST)
private Passenger passenger;
private Flight outboundFlight;
private Flight returnFlight;
As you can see, Spring/Hibernate and EJB 3.0/JPA offer roughly functionally equivalent support for persistence via ORM.
The primary difference between Spring and EJB 3.0's support for persistence may at first appear to be compatibility with different persistence frameworks. Spring provides support for many different ORM implementations. In EJB, only JPA is explicitly supported. But it is important to remember that JPA is merely a specification and many different providers support JPA including Hibernate, Kodo, and TopLink. Table 1 lists the default JPA provider for several popular EJB 3.0 application servers:
Table 1. JPA Providers Supported by Leading App Servers.
In this sense both technologies offer a choice in persistence providers. If you need to use provider-specific features that are not part of the JPA specification then you can certainly do so in your EJB application but you may sacrifice platform cross-compatibility.
Another important concept when working with ORM tools is caching. The cache is important when considering Spring and EJB 3.0 because in order for multiple components to collaborate on a single unit of work this cache must be shared between them. In Spring this is typically accomplished by applications attaching the session (and transaction) to the thread using thread local variables. Although Spring offers classes to make implementing this easier it still needs to be addressed by the development team. In EJB 3.0 by contrast the persistence context is propagated automatically on the transaction.
Object models are often rich and complex graphs. It would be inefficient if every time a persistent entity was retrieved from the database if all of its relationships were automatically fetched. To address this ORM providers often provide functionality such as lazy initialization, which defers the execution of particular database operations until the data is actually needed. This prevents a lot of unnecessary and expensive database calls but can complicate application logic quite a bit. If the cache associated with a lazily initialized collection is closed when the collection is accessed then exceptions are thrown.
The approach taken by Spring/Hibernate is to open and close the cache around view creation. Spring provides an OpenSessionInViewFilter (and interceptor) to simplify this, but it must be configured. The EJB 3.0 specification adds an interesting scope to its persistence context that allows the lifetime of the cache to be bound to the lifetime of a stateful session bean. This is named an extended persistence context and is specified by annotating the persistence context in the stateful session bean. This can simplify working with long application transactions and lazily loaded collections.
Because JPA supports pluggable persistence providers and because Spring supports those same providers directly these two approaches are very comparable. Table 2 highlights the major comparison points.
Table 2. Summation of Persistence Comparison Between Spring and EJB 3.0.
|Simple ORM Persistence
||Hibernate, JPA, JDO, TopLink, iBatis
||JPA (providers include Hibernate, Kodo and Toplink)
|Extended cache scoping
||Open session in view
||Extended persistence context
||√ (If using JPA)