Make the Right Decision with Our Side-by-Side Comparison of Spring and EJB 3.0

oftware development professionals today are faced with a bewildering array of technology choices. It is not always easy to decide which technologies are most appropriate to employ in solving any particular business problem. In the area of enterprise Java development, there has recently been quite a bit of debate regarding the Spring framework and the Enterprise Java Bean 3.0 specification. It can be hard to find clear and honest advice through the cacophony of opinion.

Still, the question of the relative strengths and weaknesses of each is a question I encounter frequently in my consulting work. But while there is plenty of information on Spring and EJB 3.0 individually, there is very little objective information that compares the two. This article will examine both technologies in relationship to one another with the goal of helping you understand the similarities and differences between them.

Spring and EJB are not the same thing. Most importantly, Spring is an implementation while EJB 3.0 is a specification. But they do have some areas of overlap, for example both provide a mechanism to deliver middleware services to Java applications. Spring was developed as a reaction against EJB and this makes comparison between the two natural. Particularly now that a new version of EJB is available, it is a good time to reevaluate how well EJB 3.0 has addressed the shortcomings of previous versions. And, to the extent that is has done so successfully, what does Spring continue to offer above and beyond EJB?

It should also be noted that EJB specifically addresses the development of distributed components whereas this emphasis is missing from the Spring definition.
This article is the first in a two-part series; each article will look at different characteristics of Spring and EJB 3. The characteristics I chose are ones that are particularly meaningful within the context of enterprise application development. I will explore each characteristic using a flight booking example application, which has a rich domain model and illustrates well enterprise challenges such as concurrency, conversations, transactionality, messaging, and scheduling. I’ve also used unit tests to determine whether Spring and EJB 3.0 offer comparable functionality.

The Definition
Before launching into a more in-depth comparison it is helpful to consider for a moment what Spring and EJB each claim to be. The Spring Web site defines Spring as “a layered Java/J2EE application framework.” And EJB claims to be “a component architecture for the development and deployment of object-oriented, distributed, enterprise-level applications” (J2EE glossary).

First, we can tell from these definitions that both intend to operate in the enterprise Java development space. But Spring claims to be an application framework whereas EJB an architecture. While what is meant by architecture varies greatly, a general definition could be the big decisions that are hard to change, or the structure of a software system. A framework implies support whereas an architecture implies decision. It may be helpful to keep these nuances in mind during the comparison. And it should also be noted that EJB specifically addresses the development of distributed components whereas this emphasis is missing from the Spring definition.

Persistence
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.

Persistence?Functional Comparison
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);        ticket.setOutboundFlight(outboundFlight);        ticket.setReturnFlight(returnFlight);        ticket.getPassenger().setFlightDetails(outboundFlight,                new PassengerFlightDetails(getSeatForFlight(outboundFlight, "2A")));        ticket.getPassenger().setFlightDetails(returnFlight,                new PassengerFlightDetails(getSeatForFlight(returnFlight, "2B")));        ticketDAO.save(ticket);        ticket = ticketDAO.findById(ticket.getId());        assertEquals("John", ticket.getPassenger().getFirstName());        assertEquals("2A", ticket.getPassenger().getDetailsForFlight(ticket.getOutboundFlight())                .getSeat().getNumber());    }

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)    {        getHibernateTemplate().saveOrUpdate(ticket);        return ticket;    }    ...}

And the corresponding EJB 3.0 implementation is seen here:

@Statelesspublic class TicketEJBDAO implements TicketDAO{    @PersistenceContext(unitName = "flightdb")    private EntityManager em;    public Ticket save(Ticket ticket)    {        em.persist(ticket);        return 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:

                                  

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:

@Entitypublic class Ticket implements Serializable{    @Id    @GeneratedValue    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.

Persistence?Non-Functional Comparison
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.

Application Server JPA Implementation
JBoss Hibernate
BEA Kodo (OpenJPA)
Oracle TopLink
Glassfish TopLink

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.

Persistence?Summary
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.

Feature Spring EJB 3.0
Simple ORM Persistence ? ?
Implementation Hibernate, JPA, JDO, TopLink, iBatis JPA (providers include Hibernate, Kodo and Toplink)
JDBC Support ?
Mapping XML, Annotations Annotations, XML
Cache Propagation Thread local Transaction
Extended cache scoping Open session in view Extended persistence context
Standard ? (If using JPA) ?

Transaction Management
Another critical factor in many enterprise applications is transaction management. This is because in high-volume applications many users may be attempting to access the same data simultaneously. And in single- and multi-user situations you may often want a particular operation to either entirely succeed or fail.

Transaction Management?Functional Comparison
To illustrate the functionality capabilities of Spring and EJB 3.0 to provide transactionality consider the case of purchasing a ticket in the flight booking scenario (see Figure 2).

Figure 2. Purchase Ticket Sequence Diagram. If any step in the purchase ticket sequence fails then the system should be left in a consistent state.

The unit test shown below verifies that the purchase ticket operation functions in a transactional manner:

    public void testPurchaseTicket_DebitFailure() throws Exception    {        // Creates a pending ticket        Ticket ticket = createTicket();        // Store original ticket count from DB        int count = ticketDAO.findAll().size();        // Setup a credit authorizer which will throw an exception on debit        setupMockAuthorizer(true);        try        {            bookingAgent.purchaseTicket(ticket, true);            fail("System failure was not thrown as was intended");        }        catch (InsufficientFundsException e)        {            // Correct behavior since we're testing transactional semantics        }        // Verify transaction rolled-back        assertEquals(count, ticketDAO.findAll().size());    }

This test can also be successfully executed against both Spring and EJB 3.0 implementations. The Spring implementation is shown below:

    public Ticket purchaseTicket(Ticket ticket)    {        creditAuthorizer.authorize(ticket.getPrice(), null);        ticket.setStatus(Status.PURCHASED);        ticketDAO.save(ticket);        creditAuthorizer.debit(ticket.getPrice());        return ticket;    }

Compare this to the EJB 3.0 implementation:

    public Ticket purchaseTicket(Ticket ticket)    {        creditAuthorizer.authorize(ticket.getPrice(), null);        ticket.setStatus(Status.PURCHASED);        ticketDAO.save(ticket);        creditAuthorizer.debit(ticket.getPrice());        return ticket;    }

Notice that they are exactly the same and that neither has to explicitly deal with transaction management! This illustrates well the value of declarative programming. Developers can write code that focuses on the business problem, and cross-cutting concerns such as transactionality can be modularized and applied across an application. In this case both Spring and EJB 3.0 offer comparable functionality in dealing with transactions and both succeed modularizing this functionality in a way that it doesn’t clutter business logic.

Transaction Management?Non-Functional Comparison
Import aspects of transaction management to consider include demarcation, propagation, and isolation. The process of bounding a unit of work with transactional semantics is termed demarcation. And if multiple components are collaborating on a single unit of work, this transaction should be shared (propagated) between them. Lastly, isolation levels allow developers to tune the degree of isolation that the transaction has from other transactions.

In Spring, transaction demarcation, propagation, and isolation are all declared through its AOP facilities. Transactional proxies can be explicitly defined in its XML configuration files or can be applied using aspect semantics. The approach of wiring in a transactional proxy is illustrated below:

                                PROPAGATION_REQUIRED                        

EJB 3.0 on the other hand applies transactional semantics automatically on all public methods of session beans so no special configuration is required. Developers can tune the transaction propagation levels via annotations or XML:

    @TransactionAttribute(TransactionAttributeType.REQUIRED)    public Ticket purchaseTicket(Ticket ticket)    {        ...    }

The EJB 3.0 specification declares transaction isolation to be resource-manager specific, thus there is no standard way to express this requirement in EJB 3.0.

Spring offers integration with many different types of transaction APIs including JDBC, Hibernate, and JTA. EJB 3.0 only supports JTA transactions. The choice of transaction API is important in two primary cases. First, not all containers (such as Tomcat) support JTA, although all EJB containers do. Second, in order to join multiple resources (such as JDBC and JMS) in a single distributed transaction, a JTA implementation is required. If you require JTA and are running in an environment that does not provide a JTA implementation you can consider open-source implementations of JTA such as the Java Open Transaction Manager (JOTM).

Transaction Management?Summary
Both Spring and EJB 3.0 offer an integrated approach to transaction management that is compatible with their persistence mechanisms (see Table 3).

Table 3. Equivalent Transaction Management Functionality in Both Spring and EJB 3.0.

Feature Spring EJB 3.0
Declarative Transactions ? ?
Programmatic Transactions ? ?
Demarcation AOP Session bean methods
Supported Transaction Types JDBC, Hibernate, JTA JTA
Support for Distributed Transactions ? (With JTA) ?
Configuration XML Transactional by default, override with annotations or XML
Standard ? (With JTA) ?

Statefulness
One of the more characteristic differences between Spring and EJB 3.0 is their respective approaches to managing state. State is an important element to many applications because a user’s interaction with the system typically involves a series of steps with cumulative meaning. For example, in the context of the example flight booking application, while booking a ticket a customer navigates between browsing for flights, selecting seat assignments, and providing payment information. As the user navigates between these steps the choices made in previous steps are implicit in the context of the conversation.

Recognizing the important role state plays in many applications, EJB provides a first level construct, the Stateful Session Bean (SFSB), dedicated to managing a stateful conversation. With SFSBs subsequent invocations of a bean share the bean instance and thus the same state. SFSBs are also designed with issues of scalability and fail-over in mind although the implementation of many of these concerns are left to specific vendor implementations. Spring does not offer any direct equivalent to SFSBs although several approaches exist for accomplishing the same result.

Statefulness?Functional Comparison
To illustrate the approaches toward state offered by Spring and EJB 3.0 consider the sequence of steps involved in booking a ticket (see Figure 3).

Figure 3. Book Ticket Sequence Diagram. Passengers book tickets through a series of interactions with a booking agent. These steps are conversational and the context of the conversation (the state) evolves with each step.

The following unit test verifies that the booking agent can be interacted with in a stateful manner:

    public void testPurchaseTicket_UseCase() throws Exception    {        // Begin purchase ticket conversation        FlightCriteria flightCriteria = new FlightCriteria("DFW", "AUS", DateUtilities                .parseDate("2006-01-15"));        bookingAgent.bookRoundTripTicket(flightCriteria);        // Select outbound flight        List outboundFlights = bookingAgent.listOutboundFlights();        assertEquals(3, outboundFlights.size());        bookingAgent.selectOutboundFlight(selectFlight(outboundFlights, 1));        // Select return flight        List returnFlights = bookingAgent.listReturnFlights();        assertEquals(3, returnFlights.size());        bookingAgent.selectReturnFlight(selectFlight(returnFlights, 2));        // Select passenger        Passenger passenger = new Passenger("Rod", "Coffin");        bookingAgent.selectPassenger(passenger);        // Select seats        bookingAgent.selectOutboundSeat(selectSeat(bookingAgent.retrieveCurrentTicket()                .getOutboundFlight(), "1B"));        bookingAgent.selectReturnSeat(selectSeat(bookingAgent.retrieveCurrentTicket()                .getReturnFlight(), "1A"));        // Purchase ticket        Ticket ticket = bookingAgent.purchaseTicket();        assertTrue(ticket.getId() != 0);    }

Both Spring and EJB 3.0 can satisfy this unit test. The Spring implementation is shown below:

public class BookingAgentSpring implements BookingAgent{    private FlightCriteria outboundCriteria;    private FlightCriteria returnCriteria;    private Ticket ticket;    public Ticket bookRoundTripTicket(FlightCriteria flightCriteria)    {        outboundCriteria = flightCriteria;        returnCriteria = reverseCriteria(flightCriteria);        ticket = new Ticket();        ticketDAO.save(ticket);        return ticket;    }    ...}

In order for Spring-managed beans to contain state, instances cannot be shared between callers. To accomplish this, the scope of stateful beans in Spring should be configured as prototype. This means that each time a bean is retrieved from a bean factory a new instance is created.

    ...

Compare with the EJB 3.0 implementation:

@Statefulpublic class BookingAgentEJB implements BookingAgent{    private FlightCriteria outboundCriteria;    private FlightCriteria returnCriteria;    private Ticket ticket;    public Ticket bookRoundTripTicket(FlightCriteria flightCriteria)    {        outboundCriteria = flightCriteria;        returnCriteria = reverseCriteria(flightCriteria);        ticket = new Ticket();        ticketDAO.save(ticket);        return ticket;    }    ...}

In both cases the booking agent can store state and participate with the caller in a conversational manner. So it is possible to create stateful behavior with both Spring and EJB 3.0 but the approaches and implications of these approaches differ significantly with one another.

Statefulness – Non-Functional Comparison
The examples above illustrate how stateful objects could be implemented in both Spring and EJB 3.0. Although functionally equivalent there is a significant difference in how state is managed. In the Spring implementation the caller stores the complete booking agent (see Figure 4), whereas in the EJB 3.0 implementation the caller is merely storing a proxy to the booking agent (see Figure 5). Because the caller in the EJB 3.0 version is only holding onto a proxy, the SFSB and its associated state exists within?and is managed by?the server.


Figure 4. Client Managed State. One approach to implementing stateful beans with Spring is to store the stateful component in the HttpSession managed by the web container.
 
Figure 5. EJB Managed State. Clients interact with EJB components via proxies. This allows the component’s state to be managed by the EJB container.

There are several advantages to the EJB approach with SFSBs. First, SFSB are designed to optimize server performance by allowing SFSBs to be swapped between memory and persistence storage. This allows application servers to balance resources to achieve optimal performance. Secondly, SFSBs can be run in a separate process from the caller. This might be performed to allow tiers of an application to be scaled independently or for security purposes. Thirdly, SFSBs may also implement the optional SessionSynchronization interface to in order to rollback bean state in response to a transaction rollback event.

One of the major criticisms typically heaped on SFSBs is that they do not perform or scale as well as stateless architectures. In response to this, the Spring framework is primarily a stateless framework. But given that state is an important part of many applications, there has to some options for expressing state. Typical approaches include putting state in the database, the HTTP Session, or in an in-memory cache. In the example above, the stateful Spring booking agent could be stored in either of these vehicles. But each of these options has its own disadvantages, as shown in Table 4.

Table 4. Alternatives to Stateful Session Beans in Spring.

SFSB Alternative Advantages Disadvantages
Database Shared by all application servers. Usually the least scalable tier of an application. If using a second-level cache then data must be replicated.
HTTP Session Simple to use. Data must be replicated and is not transaction aware.
Memory Cache Shared by all application servers, potentially more efficient then using the database. Data must be replicated and is not transaction aware.

The scalability argument applied against SFSBs can also be applied to each of these three alternatives to SFSBs (as shown in Table 4). In addition you must spend considerable development effort re-implementing functionality already provided by SFSBs. These tradeoffs should be considered seriously when deciding upon your application’s approach to state.

Both Spring and EJB 3 offer hooks that allow developers to tie into the lifecycle of managed beans. Spring provides the InitializingBean and DisposableBean interfaces, which provide hooks to notify beans after they have been initialized by Spring and before destruction. Similarly, EJB components can optionally receive notifications after creation, before destruction, before passivation, and after activation.

Lastly, the issue of how to synchronize the conversational state of a bean with the status of a transaction is one that must be resolved, whether using SFSBs or an alternative. By default, when a transaction rolls back, all transactional resources participating in the transaction (such as database and messaging systems) roll back as well. But the state of the beans involved will not be rolled back. If these beans are stateful then they will be out of sync with the persistence data store. EJB 3.0 SFSBs using container-managed transactions can implement the SessionSynchronization interface to be notified of transactional events. Spring does not offer equivalent functionality but it could be handled in some custom way by the application.

Statefulness?Summary
Both Spring and EJB 3.0 enable stateful applications. In an EJB 3.0 application the primary mechanism for accomplishing this is the use of SFSBs. Spring is primarily a stateless architecture but can facilitate statefulness through prototype beans. Applications using this approach need to consider its implications for performance and scalability and will need some other method of addressing the issues handled by SFSBs.

Table 5. Differences in State-handling Between Spring and EJB 3.0.

Feature Spring EJB 3.0
Stateful construct Prototype (instance) beans Stateful Session Bean
Instance Management Singleton, prototype, request, session, global session Instance per lookup
Lifecycle Management ? (Initialization/Destruction) ? (Creation/Destruction, Activation/Passivation)
Transaction awareness ? (SessionSynchronization interface)
Standard N/A ?

Wrapping It Up
In this article I’ve begun to compare the Spring application framework and the EJB 3.0 specification, focusing on support for persistence, transaction management, and statefulness. Interestingly both technologies address these issues well, and the commonalities underscore the validity of the comparison.

At this point I have illustrated some of the many similarities between Spring and EJB 3.0. The main differences observed have been state management, configurability, and extensibility.

If you are working with applications that are highly stateful then you may want to consider whether EJB 3.0 SFSBs might be a good solution. For highly conversational applications you may want to consider SEAM, which provides a very powerful solution for conversational interaction built on SFSBs and JSF.

Spring gives you more flexibility in many aspects of application development than EJB does?and as you’ve seen in this article, this is particularly true with regards to persistence and transaction providers. But the trade-off for this added flexibility is increased complexity in configuration. EJB 3.0 provides less flexibility but its tight technology stack, annotations-based configuration, and philosophy of configuration by exception make configuring EJB 3.0 applications quite simple. If flexibility is more important to you than a pre-defined approach then you certainly will want to consider Spring.

A final point on standardization: While Spring integrates many standards such as JTA, JDBC, and JMS it is not itself a Java standard. If standardization (and by extension vendor support, tooling, etc.) is important to your organization or application then you will want to consider EJB 3.0.

But luckily Spring and EJB 3.0 are not mutually exclusive choices. There are very powerful ways of integrating these two technologies to take advantage of their relative strengths and weaknesses. In part 2 in this series (coming soon) I will examine additional characteristics, including messaging, remoting, scheduling, dependency management, and intermediation. After the comparison is complete I will make some general observations that can assist in making a technical decision regarding Spring and EJB 3.0. And lastly, I will point out several strategies for integrating these technologies in order to take advantage of features of each technology.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts