Using Spring to develop EJB 3.1
When developping EJB applications, you need to bootstrap an EJB container to run your EJBs. Before Java EE 6, EJB containers usually come bundled in full-blown JEE application servers, making the test and development of EJBs difficult and counter-productive. As of Java EE 6, the EJB specification comes with a new feature : an embedded API for EJB containers. This is a great step, but I found some limitations to this API (more on that later). That’s why I decided to code an stateless EJB (arguably the most commonly kind of EJB), test it with Spring and run it under Glasshfish.
Roadmap
The EJB will be at the heart of a very simple web application. It will allow to add and select contact entities. We’ll be using JPA and Derby for the persistence. We’ll also leverage the new web profile of Java EE, as the EJB will be distributed directly in the web application, along with the JPA persistence unit and a servlet (to display contacts).
The entity
Here is the code of the entity :
-
package com.zenika.contact.domain;
-
import javax.persistence.Entity;
-
import javax.persistence.GeneratedValue;
-
import javax.persistence.GenerationType;
-
import javax.persistence.Id;
-
@Entity
-
public class Contact {
-
@Id
-
@GeneratedValue(strategy=GenerationType.IDENTITY)
-
private Long id;
-
private String firstName,lastName;
-
public Contact() { }
-
public Contact(String firstName, String lastName) {
-
this();
-
this.firstName = firstName;
-
this.lastName = lastName;
-
}
-
// getters and setters…
-
}
The EJB
Here is the interface of the EJB :
-
package com.zenika.contact.service;
-
import java.util.List;
-
import com.zenika.contact.domain.Contact;
-
public interface ContactService {
-
/**
-
* Contact creation.
-
* @param contact
-
* @return
-
*/
-
Contact create(Contact contact);
-
/**
-
* Contact selection.
-
* @return
-
*/
-
List<Contact> select();
-
}
Note this is a plain interface (not remote nor locale), because I’m used to coding against interface, but we could also use the corresponding EJB annotations if we needed to.
And now, the code of the EJB itself :
-
package com.zenika.contact.service.impl;
-
import java.util.List;
-
import javax.ejb.Stateless;
-
import javax.persistence.EntityManager;
-
import javax.persistence.PersistenceContext;
-
import com.zenika.contact.domain.Contact;
-
import com.zenika.contact.service.ContactService;
-
@Stateless
-
public class ContactServiceImpl implements ContactService {
-
@PersistenceContext(name=“contact”)
-
private EntityManager entityManager;
-
@Override
-
public Contact create(Contact contact) {
-
entityManager.persist(contact);
-
return contact;
-
}
-
@Override
-
public List<Contact> select() {
-
return entityManager.createQuery(“select c from Contact c”).getResultList();
-
}
-
}
The JPA configuration
The JPA configuration lies in the standard location, META-INF/persistence.xml
:
-
<?xml version=“1.0” encoding=“UTF-8”?>
-
<persistence xmlns=“http://java.sun.com/xml/ns/persistence” version=“1.0”>
-
<persistence-unit name=“contact” transaction-type=“JTA”>
-
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
-
<jta-data-source>jdbc/contactDS</jta-data-source>
-
<class>com.zenika.contact.domain.Contact</class>
-
<exclude-unlisted-classes />
-
<properties>
-
<property name=“eclipselink.ddl-generation” value=“drop-and-create-tables”/>
-
<property name=“eclipselink.logging.level” value=“INFO”/>
-
</properties>
-
</persistence-unit>
-
</persistence>
Note the persistence unit uses JTA transaction, EclipseLink (JPA 2.0 Reference Implementation, which ships with Glassfish 3.0) and a JNDI datasource.
The servlet
The servlet is only there to check that everything goes fine once the application has been deployed :
-
package com.zenika.contact.web;
-
import java.io.IOException;
-
import java.util.List;
-
import javax.ejb.EJB;
-
import javax.servlet.ServletException;
-
import javax.servlet.http.HttpServlet;
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import com.zenika.contact.domain.Contact;
-
import com.zenika.contact.service.ContactService;
-
public class ContactServlet extends HttpServlet {
-
@EJB
-
private ContactService contactService;
-
@Override
-
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-
throws ServletException, IOException {
-
List<Contact> contacts = contactService.select();
-
if(contacts.isEmpty()) {
-
resp.getWriter().append(“<p>No contact.</p>”);
-
25; else {
-
for(Contact contact : contacts) {
-
resp.getWriter().append(“<p>”)
-
.append(contact.getFirstName())
-
.append(” “)
-
.append(contact.getLastName())
-
.append(“</p>”);
-
}
-
}
-
}
-
}
There’s also a servlet listener, to populate the database :
-
package com.zenika.contact.web;
-
import javax.ejb.EJB;
-
import javax.servlet.ServletContextEvent;
-
import javax.servlet.ServletContextListener;
-
import com.zenika.contact.domain.Contact;
-
import com.zenika.contact.service.ContactService;
-
public class PopulateDataBaseListener implements ServletContextListener {
-
@EJB
-
private ContactService contactService;
-
@Override
-
public void contextInitialized(ServletContextEvent sce) {
-
if(contactService.select().isEmpty()) {
-
contactService.create(< span style=”color: #7F0055; font-weight: bold;”>new Contact(“Arnaud”,“Cogoluegnes”));
-
contactService.create(new Contact(“Carl”,“Azoury”));
-
contactService.create(new Contact(“Olivier”,“Croisier”));
-
}
-
}
-
@Override
-
public void contextDestroyed(ServletContextEvent sce) { }
-
}
The web.xml of the application :
-
<web-app xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
-
xsi:schemaLocation=“http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd”
-
id=“rest” version=“2.5”>
-
<display-name>Zen Contact JEE</display-name>
-
<listener>
-
<description>Add some rows in the database if necessary.</description>
-
<listener-class>com.zenika.contact.web.PopulateDataBaseListener</listener-class>
-
</listener>
-
<servlet>
-
<servlet-name>contact</servlet-name>
-
<servlet-class>com.zenika.contact.web.ContactServlet</servlet-class>
-
</servlet>
-
<servlet-mapping>
-
<servlet-name>contact</servlet-name>
-
<url-pattern>/contact.do</url-pattern>
-
</servlet-mapping>
-
</web-app>
By now, you should have something like the following :
Setting up the database
We’ll be using Derby, which ships with the JDK (as of version 1.6). Go to JAVA_HOME/db/bin and start the server with the startNetworkServer
script.
Testing the EJB with Spring
Let’s say our EJB is much more complicated, with complex queries and business algorithms. Thus we need to test it against the database. The embedded EJB container API works but I found the one from Glassfish quite long to start and it remains difficult to switch from a test database to the server database, as the container picks up by default the META-INF/persistence.xml
, which points to a JNDI datasource. Moreover, what we need in our test are transaction management and dependency injection (for the PersistenceContext
). Spring offers a lightweight yet powerful solution to do that.
Configuring JPA with Spring
We can start by declaring the EJB in the Spring application context :
-
<?xml version=“1.0” encoding=“UTF-8”?>
-
<beans xmlns=“http://www.springframework.org/schema/beans”
-
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:aop=“http://www.springframework.org/schema/aop”
-
xmlns:context=“http://www.springframework.org/schema/context” xmlns:tx=“http://www.springframework.org/schema/tx”
-
<bean class=“com.zenika.contact.service.impl.ContactServiceImpl” />
-
<context:annotation-config />
-
</beans>
Note the use of the context:annotation-config
element, which tells Spring to automatically inject PersistenceContext
s.
Thanks to Spring JPA support, we can use a specific persistence file, let’s call it persistence-test.xml
:
-
<?xml version=“1.0” encoding=“UTF-8”?>
-
<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
-
version=“1.0”>
-
<persistence-unit name=“contact” transaction-type=“RESOURCE_LOCAL”>
-
<provider>org.hibernate.ejb.HibernatePersistence</provider>
-
<class>com.zenika.contact.domain.Contact</class>
-
<exclude-unlisted-classes />
-
</persistence-unit>
-
</persistence>
Why do we use Hibernate for our test? Mainly because it enhances classes at runtime and does not need specific feature like load-time weaving through a JVM agent.
Let’s see now how to complement the Spring configuration :
-
<bean (…)>
-
(…)
-
<bean id=“dataSource”
-
class=“org.springframework.jdbc.datasource.SingleConnectionDataSource”>
-
<property name=“driverClassName” value=“org.apache.derby.jdbc.ClientDriver” />
-
<property name=“url”
-
value=“jdbc:derby://localhost:1527/contact_test;create=true” />
-
<property name=“username” value=“APP” />
-
<property name=“password” value=“APP” />
-
<property name=“suppressClose” value=“true” />
-
</bean>
-
<bean id=“contactEntityManagerFactory”
-
class=“org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean”>
-
<property name=“persistenceXmlLocation” value=“/com/zenika/contact/service/impl/persistence-test.xml” />
-
<property name=“dataSource” ref=“dataSource” />
-
<property name=“jpaProperties”>
-
<props>
-
<prop key=“hibernate.dialect”>org.hibernate.dialect.DerbyDialect</prop>
-
<prop key=“hibernate.hbm2ddl.auto”>create</prop>
-
<prop key=“hibernate.show_sql”>false</prop>
-
</props>
-
</property>
-
<property name=“jpaDialect”>
-
<bean class=“org.springframework.orm.jpa.vendor.HibernateJpaDialect” />
-
</property>
-
</bean>
-
</beans>
What about transaction management? We can add it declaratively, with the Spring JPA transaction manager and by decorating Spring beans that hold the @Stateless
annotation :
-
<bean (…)>
-
(…)
-
<bean id=“transactionManager” class=“org.springframework.orm.jpa.JpaTransactionManager”>
-
<property name=“entityManagerFactory” ref=“contactEntityManagerFactory” />
-
</bean>
-
<aop:config>
-
<aop:pointcut expression=“within(@javax.ejb.Stateless *)” id=“ejbPointcut”/>
-
<aop:advisor advice-ref=“transactionInterceptor” pointcut-ref=“ejbPointcut” />
-
</aop:config>
-
<tx:advice id=“transactionInterceptor”>
-
<tx:attributes>
-
<tx:method name=“*” />
-
</tx:attributes>
-
</tx:advice>
-
</beans>
Writing the test
We’re ready now to test the EJB. The Spring TestContext Framework can handle the Spring application context lifecycle and would even cache it if other tests were using it, making the test suite faster. Here is the test :
-
package com.zenika.contact.service.impl;
-
import javax.sql.DataSource;
-
import org.junit.Assert;
-
import org.junit.Test;
-
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.jdbc.core.JdbcTemplate;
-
import org.springframework.test.context.ContextConfiguration;
-
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
import com.zenika.contact.domain.Contact;
-
import com.zenika.contact.service.ContactService;
-
@RunWith(SpringJUnit4ClassRunner.class)
-
@ContextConfiguration
-
public class ContactServiceSpringTest {
-
@Autowired
-
ContactService contactService;
-
@Autowired
-
DataSource dataSource;
-
@Test public void create() {
-
JdbcTemplate template = new JdbcTemplate(dataSource);
-
int count = template.queryForInt(“select count(1) from contact”);
-
Contact contact = new Contact();
-
contact.setFirstName(“Arnaud”);
-
contact.setLastName(“Cogoluegnes”);
-
contactService.create(contact);
-
Assert.assertEquals(count+1,template.queryForInt(“select count(1) from contact”));
-
}
-
@Test public void select() {
-
JdbcTemplate template = new JdbcTemplate(dataSource);
-
template.update(“delete from contact”);
-
template.update(“insert into contact (id, firstName, lastName) values (default,?,?)”, “Arnaud”,“Cogoluegnes”);
-
Assert.assertEquals(1,contactService.select().size());
-
}
-
}
You can now run the test with Eclipse for instance.
Running the application on Glassfish
Before deploying the application, you need to install Glassfish and configure the JNDI datasource.
Setting up Glassfish
You can download Glassfish from its web page. We use here Glassfish 3.0. Once you have the archive, unzip it in a directory, go to the bin
directory and start the server :
./asadmin start-domain
You can then access the admin console from the following URL: http://localhost:4848/
Setting up the datasource
The set up of the datasource is made of two steps: the pool and then the JDBC resource. Create first a pool for Derby, which uses the contact database (and creates it if necessary) :
And then, create the JDBC resources that uses this connection pool :
Deploying the application
Once packaged (see source code enclosed to this post), the application can be deployed on Glassfish. All the components we wrote have been detected :
You can check the application at http://localhost:8080/jeecontact-1.0.0/contact.do
Conclusion
Spring is not only able to power applications from the data access layer to the UI layer, it can also help you developping transactional components like EJBs. This is especially valuable when queries become complex and you want to clearly separate the test environment from the server environment, in a highly configurable and simple way.
Hmmm. 80+ lines of XML ? 😉
I’d suggest using @Startup or @PostConstruct to populate the database and removing web.xml altogether.
Also, look into CDI’s @Alternative for switching between databases.