This is a step-by-step guide for converting an old Enterprise Java Beans (EJB) application (EJB versions 1 and 2 in mind) to use Spring Framework and Hibernate.

The aim of this article is to tell in a practical and detailed way how this can be done. The question of why it is sometimes a good idea has been widely discussed, but nevertheless I address it briefly in an earlier post.

Please bear in mind that you always need to know what you are doing, and to adapt this procedure to your environment. Also, this kind of work should only be done when it clearly provides value. My experience has been that as with any refactorings or other functionality-preserving changes, this kind of improvement is best done in conjunction with changing or adding functionality, when the code needs to be touched upon anyway.

For the sake of the example, I created a little EJB 2 application and deployed it on JBoss. You can check the application out from or browse it in Subversion. For building and running the application, there are instructions in README.txt.

The main application PersonFinderApp is a command line program for searching persons in the database:

    public static void main(String... args) {
        if (args.length < 1) {
            throw new IllegalArgumentException(
            "Please supply a search term as a command-line parameter");
        }

        String searchTerm = args[0];
        List results = instance.findPersons(new SearchParameters(searchTerm));
        for (Person result : results) {
            try {
                out.println(MessageFormat.format("{0}, {1} {2} ({3})", result,
                    result.getFirstName(), result.getLastName(), result.getUserName()));
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }
    }

This is done as follows:

  • PersonFinderApp gets a stateless session EJB PersonManager using container-managed transactions (CMT)
  • PersonManager invokes the finder method findBySearchTerm of PersonEntity EJB to get the results
  • PersonFinderApp outputs the results

PersonEntity is a container-managed (CMP) entity EJB. Its home interface provides methods for creating a new entity, finding an entity by its primary key, and finding an entity by a search string with EJB QL. The finder EJB QL is specified in ejb-jar.xml that contains all EJB configuration. The EJB container of JBoss handles the persistence of PersonEntity objects to a Hsqldb database embedded in JBoss.

So this simplistic example provides the two common cases to convert to non-EJB solutions: a stateless session bean that takes care of transaction management and service functionality, and an entity bean that handles database persistence of domain objects.

Prerequisites

Before doing anything, ensure that you have good automatic, in-container regression tests in place verifying that the EJB application works as expected. I have written these as JDave specifications. For example PersonFinderAppSpec.InContainer tests the main application which uses the deployed EJBs on the container:

public PersonFinderApp create() throws RemoteException {
    PersonDao dao = new ServiceLocatorImpl().getPersonDao();
    person = dao.create("appPerson-" + System.currentTimeMillis(), "Bill", "Apperson");
    return new PersonFinderApp();
}

public void destroy() throws RemoveException, RemoteException {
    person.remove();
}

public void findsPersonsFromDatabase() {
    List results = context.findPersons(new SearchParameters("Bill"));
    specify(results, contains(person));
}

There are also more fine-grained specifications.

Changing the stateless session bean to a Spring bean

I’ll start with the easier case of converting PersonManager to a normal Spring bean. First I’m adding a dependency to Spring 2.0.6 in the project pom.xml files and adding to the shiny new applicationContext.xml a Spring transaction manager bean that the new Spring bean will use. For this, I add these specification methods to ServiceLocatorImplSpec:

public void getsSpringApplicationContext() {
    specify(context.getApplicationContext(), does.not().equal(null));
}

public void getsSpringTransactionManagerFromApplicationContext() {
    ApplicationContext applicationContext = context.getApplicationContext();
    specify(applicationContext.getBean("txManager"), does.not().equal(null));
}

For the sake of completeness, I’m first making it use the JTA Transaction manager of JBoss. In the case of this little application, and also in many real-world-cases, transactions are not so complex that they would have both Spring beans and session EJBs operating within the same transaction. But if this happens, it’s good to know that Spring supports using JTA from the application server for its own declarative transaction management. In our example application that uses a remote client, there is a glitch that on client-side, you can only use UserTransaction because the actual JTA transaction manager is only available within the server. For server-side JTA transactions you would put

       <property name="transactionManagerName" value="java:/TransactionManager"/>

for the Spring txManager bean (JtaTransactionManager). The user transaction which we will use is configured like this:

  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"
dependency-check="none">
    <property name="autodetectTransactionManager" value="false"/>
    <property name="userTransactionName" value="UserTransaction"/>

I also make a new ApplicationContextHolder class with acts as the glue code singleton for our legacy app. It takes care of creating the Spring application context from the XML configuration:

public class ApplicationContextHolder {
    private static ApplicationContext context;

    public static ApplicationContext getContext() {
        if (context == null) {
            initContext();
        }
        return context;
    }

    private static void initContext() {
        context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }
}

ServiceLocatorImpl.getApplicationContext() just delegates to this class. The implementation is not thread safe, so it assumes that Spring context usage does not start on many threads at the same time.

To make things easier, PersonManager uses some business interfaces to abstract the EJB access. The business interfaces are PersonFinder and PersonDao. This is good practice and often refactorable to existing EJB applications, if not present. The only problem with old remote EJBs is that the method signatures get littered with throws RemoteException.

And now to changing the actual bean: I first create another specification method in PersonManagerSpec:

public void isFoundInSpringApplicationContext() {
    ApplicationContext applicationContext = new ServiceLocatorImpl().getApplicationContext();
    specify(applicationContext.getBean(PersonManager.ID, PersonManager.class), does.not().equal(null));
}

and it fails as expected:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'personManager' is defined

Now I remove EJBObject from the PersonManager extends list and make PersonManagerBean implement it directly, instead of SessionBean and the business interface PersonFinder (because PersonManager already extends PersonFinder). I also remove throws CreateException from the create method signature and handle it inside the method, because it now clashes with the signature of PersonDao.create() — but actually all EJB-specific throws clauses can now be removed.

PersonManagerBean still needs the @Transactional annotation to get declarative transaction management by Spring, and the annotation in turn requires

  <tx:annotation-driven transaction-manager="txManager"/>

in applicationContext.xml.

Now it’s time to add the actual Spring bean configuration for PersonManager:

  <bean id="personManager" class="org.laughingpanda.ejb_migration_example.ejb.PersonManagerBean"/>

IDEA now complains that I should disable dependency checking or set the property sessionContext. So I delete the redundant setter method, and all the remaining EJB API methods as well, from PersonManagerBean.

And now the test passes in the IDE! However, I still need to change ServiceLocatorImpl to use the new Spring bean, and the stateless session bean needs to be removed from ejb-jar.xml to get the EJB build pass, and to remove the previously deployed EJB.

So I delete the whole bean definition from ejb-jar.xml and change the JNDI lookup

private PersonManager getPersonManager() {
    Object objref = lookup("PersonManagerEJB");
    PersonManagerHome home = (PersonManagerHome) PortableRemoteObject.narrow(objref, PersonManagerHome.class);
    try {
        return home.create();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

to Spring application context lookup

private PersonManager getPersonManager() {
    return (PersonManager) getApplicationContext().getBean(PersonManager.ID);

and rebuild and deploy, and now everything works!

I now remove the unnecessary throws RemoteException clauses from the business interfaces, and their handling from PersonFinderApp.findPersons method, and after testing the application with IDE and Maven and from command-line, our work on PersonManager is done for now.

The procedure might seem involved, but is in fact almost mechanical. Because it must be done manually, it’s important to have automatic regression tests for the actual functionality, as well as to check the relevant functionality manually. For example classloader issues may arise because code might get moved from the EJB classloader to another one lower on the classloader hierarchy.

To recap, to change a session bean to a Spring-managed Java bean

  1. set up Spring and add the necessary service lookups or configurations there (transaction manager, datasource…)
  2. make Spring context available to your service locator via a singleton class holding the application context
  3. ensure that you have a regression test for the functionality that uses the relevant EJB, and run it to verify that it passes
  4. create a new, failing integration test for getting the (soon-ex-enterprise) bean from Spring context
  5. remove EJB-specific things from the local or remote EJB interface
  6. remove EJB-specific things from the bean class, and make it directly implement its interface
  7. add the bean, that is now a plain old java object (POJO), to Spring
  8. add Spring transaction management to the bean
  9. change the service locator to lookup the Spring bean instead of the EJB
  10. remove the old session bean from ejb-jar.xml
  11. do a clean build + deploy, run the regression test and correct errors if any
  12. verify that the real application still works as expected
  13. check that anything looks clean and remove any extra warts left behind, such as the old EJB home interface

When removing more EJBs after the first one, you skip the two first steps, as the Spring plumbing is already in place. But when the beans start to get dependencies on each other, you should change them to let Spring inject them instead of the Spring beans looking up other Spring beans via the service locator. Below there will be an example of this.

The current state of the software is in the 0.2 tag in Subversion.

Personmanager is now a Spring-managed POJO, even though it incidentally still uses the PersonEntity EJB. So that’s what we’ll turn to next.

Changing the entity bean to a POJO + Hibernate DAO

I start by putting Hibernate dependency to the pom.xml and updating it to the IDE configuration files. Then I make a failing integration spec for the soon-to-be Spring bean PersonDao

@RunWith(JDaveRunner.class)
public class PersonDaoSpec extends Specification {
    public class InSpring {
        public PersonDao create() {
            return null;
        }

        public void isFoundInApplicationContext() {
            ApplicationContext applicationContext = ApplicationContextHolder.getContext();
            PersonDao fromSpring = (PersonDao)
                    applicationContext.getBean(PersonDao.ID, PersonDao.class);
            specify(fromSpring, does.not().equal(null));
        }
    }

I then start coding a Hibernate implementation of PersonDao, namely PersonHibernateDao. To get some sense to the method public PersonEntity create(String userName, String firstName, String lastName) I make a simple java bean out of PersonEntityBean, making it implement PersonEntity directly. If you are using data transfer objects (DTO), as is common in EJB 1 and EJB 2 applications, they can be used as a basis for the domain object implementations.

I also remove extends EJBObject from PersonEntity. While implementing the create method, I notice that all methods in Person throw RemoteException, which can now be removed.

For now, I end up with a Hibernate DAO class like this:

    public PersonEntity create(String userName, String firstName, String lastName) {
        PersonEntity person = new PersonEntityBean();
        person.setUserName(userName);
        person.setFirstName(firstName);
        person.setLastName(lastName);

        Session session = sessionFactory.getCurrentSession();
        Long id = (Long) session.save(person);
        return (PersonEntity) session.get(PersonEntityBean.class,id);
    }

I realise that I have to change PersonEntitySpec to use the DAO, and add the remove method in PersonDao.

Then I add the new DAO and Hibernate configuration to the Spring configuration file:

  <jee:jndi-lookup id="dataSource" jndi-name="DefaultDS"/>
  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
        dependency-check="none">
    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/>
    <property name="annotatedClasses">
      <list>
        <value>org.laughingpanda.ejb_migration_example.PersonBean</value>
      </list>
    <property name="schemaUpdate" value="true"/>
    <property name="hibernateProperties">
        <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
        <prop key="hibernate.show_sql">false</prop>
        <prop key="hibernate.cglib.use_reflection_optimizer">false</prop>
        <prop key="hibernate.query.substitutions">true=1, false=0</prop>
        <prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
    </property>

  <bean id="personDao" class="org.laughingpanda.ejb_migration_example.PersonHibernateDao"
          autowire="constructor"/>

and now the bean is found in Spring context.

Now, because the EJB classes are now longer EJB classes on the client, PersonEntitySpec starts failing. I need to change PersonManagerBean to use the new DAO instead of PersonEntityHome. So I do that, and add the rest of PersonEntityHome methods to PersonDao, and change ServiceLocatorImpl.getPersonDao() to return the personDao Spring bean.

When running the specs again, Hibernate tells us that there is no mapping for PersonEntityBean:

org.hibernate.MappingException: Unknown entity: org.laughingpanda.ejb_migration_example.ejb.PersonEntityBean

So I tag the relevant portions of PersonEntityBean with the JPA annotations and creating new lines to the database now works.

The finder method still needs to be implemented with Hibernate, for which I use a simple Hibernate Query Language (HQL) query

    public Collection findBySearchTerm(String searchTerm) throws FinderException, RemoteException {
        return getSession().createQuery("from PersonEntityBean person " +
                "where userName like ? or firstName like ? or lastName like ?")
                .setString(0, searchTerm).setString(1, searchTerm).setString(2, searchTerm)
                .list();
    }

and also make simple equals, hashCode and toString implementations. And now all tests pass again in the IDE!

Deploying the application still fails, because the entity bean needs to be removed from ejb-jar.xml. Empty enterprise-beans element would not be valid, so I end up deleting the whole file. This in turn makes the EJB build fail, so I remove the whole person-ejb module from the pom.xml files. Now the tests pass both from IDE and from Maven, and the project can be built and deployed. And it still works from the command-line too.

Now it’s time to clean up. First of all, there are a lot of unused throws clauses that I remove, and PersonEntityHome can be thrown away as well. While we’re at it, I also remove PersonMangerHome that I forgot when converting PersonManagerBean. PersonEntity interface is now redundant

public interface PersonEntity extends Person {
}

so I inline that as well. I don’t know if the Person interface makes any sense either, but leave it for now. I rename PersonEntityBean to PersonBean, though, and remove the ejbCreate and other EJB methods from it. It can now be used in PersonFinderAppSpec instead of the inner class PersonStub used until now.

PersonManagerBean still looks up personDao from ServiceLocator, so I replace that with autowiring by name now that personDao is a Spring-managed bean as well. This causes PersonManagerSpec to fail, so I make it get the bean from application context instead of constructing it by itself. But then again, PersonManagerBean doesn’t really do anything interesting other than delegate to personDao, except this method

public List findPersons(SearchParameters searchParameters) {
    try {
        String termWithWildcard = searchParameters.getSearchTerm() + "%";
        Collection rawResults = personDao.findBySearchTerm(termWithWildcard);
        List results = new ArrayList(rawResults.size());
        for (Object o : rawResults) {
            results.add((Person) o);
        }
        return results;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

so I just move the method to PersonHibernateDao, make PersonDao extend PersonFinder, and remove the whole PersonManager. This enties moving relevant PersonManagerSpec contents to PersonDaoSpec as well.

This can be done, because

  • Unlike the container-managed entity bean PersonEntity EJB, the new PersonHibernateDao is a POJO to which I can add arbitrary methods taking arbitrary kind of parameters — in this case, a finder method taking in a custom java class SearchParameters.
  • With the declarative transaction management of Spring, we can make the DAO itself transactional and customise its transaction attributes as needed (on Java 5, simply by adding the @Transactional annotation to it). Indeed my co-worker Joni has shown me that in many cases, you can just make the DAOs transactional, and use them directly from the client code. Separate services are only really needed when you need more complex transactions spanning across different transactional services, and even then it is often viable to use ad hoc transactions, for example with the handy TransactionTemplate from Spring.

After some cleanup, the whole PersonHibernateDao is quite lean.

So, to change an entity EJB to a Hibernate-persisted POJO:

  1. set up Hibernate
  2. ensure that you have a regression test for the functionality that uses the relevant EJB, and run it to verify that it passes
  3. create a new, failing integration test for getting the soon-to-be Hibernate DAO from Spring context
  4. remove EJB-specific things from the local or remote EJB interface
  5. create a POJO implementation of your domain objects, using the local or remote EJB interface or the DTO class as the base, and make sure to have good equals and hashCode implementations for it
  6. create the new Hibernate DAO, moving the relevant Home interface methods to it
  7. add the DAO to Spring
  8. change the service locator to lookup this new Spring bean instead of the EJB
  9. verify that tests using the DAO now fail because the domain object class is not mapped yet
  10. add Hibernate mapping for the domain object class, either by JPA annotations or manual .hbm.xml mapping files
  11. run the tests using the DAO and correct errors if any
  12. remove the entity EJB from the ejb-jar.xml
  13. do a clean build + deploy, run the regression test and correct errors if any
  14. verify that the real application still works as expected

Now that we don’t have any EJBs left, I also remove the whole person-ejb module. The current state of the project is in the 0.3 tag in Subversion.

Removing the rest of the container dependencies for easier development

The application still uses DataSource and transaction manager from the application server. It doesn’t use the EJB container any more, so it could already be run on an express version of a commercial application server, but requires the application server to be running to even run the integration tests.

So I take Apache Commons DBCP connction pool in use, and replace looking up the JBoss DataSource from JNDI with the commons-dbcp BasicDataSource. I also change the Spring transaction manager from the JTA transaction manager to HibernateTransactionManager.

  <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"/>
  <tx:annotation-driven transaction-manager="txManager"/>

  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:hsql://localhost:1701"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
    <property name="defaultAutoCommit" value="false"/>

In ServiceLocatorImpl, there was still a JNDI lookup for the DataSource, so I replace it with a lookup to the Spring ApplicationContext. I run the tests from IDE, rebuild the application and run the tests with maven, run the application, and everything still works.

But the real acid test of running the tests with JBoss shut down still fails, because Hsqldb is being run in server mode within JBoss. Fortunately separating settings between test and production mode is easy enough in Spring:

  <bean id="dataSourceProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="datasource.properties" />

  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
    <property name="defaultAutoCommit" value="false"/>

I put the server mode configuration in the production version of datasource.properties, and the in-memory-version in the test source path, and now the tests run in the IDE! In Maven 2.0.7, the test classpath override doesn’t yet work, so for now Maven still requires the separate database instance. Anyway, all code may now be easily developed just within the IDE.

I also remove all JBoss and EJB references from the project now that they are not needed any more, and have a general look on what the application is like at the moment. At least PersonBean doesn’t need to be in the ejb subpackage any more, and the whole package can be removed as it is now empty. Then I tag the version 0.4 where I stop.

All questions, corrections and comments are most welcome. If you apply these instructions, be sure to have good regression tests, to move in little steps, and to know what you’re doing on each step. Happy EJB-hunting!

This article is dedicated to my workmate Markus Hjort.

Advertisement