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
- set up Spring and add the necessary service lookups or configurations there (transaction manager, datasource…)
- make Spring context available to your service locator via a singleton class holding the application context
- ensure that you have a regression test for the functionality that uses the relevant EJB, and run it to verify that it passes
- create a new, failing integration test for getting the (soon-ex-enterprise) bean from Spring context
- remove EJB-specific things from the local or remote EJB interface
- remove EJB-specific things from the bean class, and make it directly implement its interface
- add the bean, that is now a plain old java object (POJO), to Spring
- add Spring transaction management to the bean
- change the service locator to lookup the Spring bean instead of the EJB
- remove the old session bean from
ejb-jar.xml
- do a clean build + deploy, run the regression test and correct errors if any
- verify that the real application still works as expected
- 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:
- set up Hibernate
- ensure that you have a regression test for the functionality that uses the relevant EJB, and run it to verify that it passes
- create a new, failing integration test for getting the soon-to-be Hibernate DAO from Spring context
- remove EJB-specific things from the local or remote EJB interface
- 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
- create the new Hibernate DAO, moving the relevant Home interface methods to it
- add the DAO to Spring
- change the service locator to lookup this new Spring bean instead of the EJB
- verify that tests using the DAO now fail because the domain object class is not mapped yet
- add Hibernate mapping for the domain object class, either by JPA annotations or manual
.hbm.xml
mapping files
- run the tests using the DAO and correct errors if any
- remove the entity EJB from the
ejb-jar.xml
- do a clean build + deploy, run the regression test and correct errors if any
- 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.