Today I read that Spring-JPA is the 'dream team' for POJO development. So I thought I'd see what all the hype is about. Here is a skeleton Maven project using Spring, JPA implemented with Hibernate and backed with an embedded Derby database.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.adrianwalker.maven.skeleton.spring.jpa</groupId> <artifactId>maven-spring-jpa-skeleton</artifactId> <version>0.1.0</version> <name>Maven Spring JPA Skeleton</name> <description> Skeleton project for using Spring, Hibernate and Derby database accessed through JPA. Usage: mvn clean install </description> <url>http://www.adrianwalker.org</url> <organization> <name>adrianwalker.org</name> <url>http://www.adrianwalker.org</url> </organization> <developers> <developer> <name>Adrian Walker</name> <email>ady.walker@gmail.com</email> <organization>adrianwalker.org</organization> <organizationUrl>http://www.adrianwalker.org</organizationUrl> </developer> </developers> <repositories> <repository> <id>java.net</id> <url>http://download.java.net/maven/2</url> </repository> <repository> <id>java.net - legacy</id> <url>http://download.java.net/maven/1</url> <layout>legacy</layout> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.6.1.0</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.1.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.2.1.ga</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> <scope>test</scope> </dependency> </dependencies> </project>
Spring Configuration
context.xml
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver" /> <property name="url" value="jdbc:derby:target/database/message;create=true" /> <property name="username" value="app" /> <property name="password" value="app" /> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="databasePlatform" value="org.hibernate.dialect.DerbyDialect" /> </bean> </property> <property name="jpaProperties"> <props> <prop key="hibernate.hbm2ddl.auto">create-drop</prop> </props> </property> </bean> <bean name="jpaMessageDao" class="org.adrianwalker.maven.skeleton.spring.jpa.JpaMessageDao"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean name="messageJpaController" class="org.adrianwalker.maven.skeleton.spring.jpa.MessageJpaController"> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> <property name="dataSource" ref="dataSource" /> </bean> </beans>
Persistence Configuration
persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" 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 http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="messagePersistenceUnit" transaction-type="RESOURCE_LOCAL"> <class>org.adrianwalker.maven.skeleton.spring.jpa.MessageEntity</class> </persistence-unit> </persistence>
JPA Entity
A very simple entity which has an id and some message text.
MessageEntity.java
package org.adrianwalker.maven.skeleton.spring.jpa; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; @Entity @NamedQueries({ @NamedQuery( name="MessageEntity.find", query = "SELECT m FROM MessageEntity m"), @NamedQuery( name = "MessageEntity.count", query = "SELECT COUNT(m) FROM MessageEntity m") }) public class MessageEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String text; public MessageEntity() { } public MessageEntity(final String text) { this.text = text; } public long getId() { return id; } public void setId(final long id) { this.id = id; } public String getText() { return text; } public void setText(final String text) { this.text = text; } @Override public String toString() { return "MessageEntity [id=" + id + ", text=" + text + "]"; } }
Spring DAO vs JPA Controller
Spring provides a JpaDaoSupport
class for extension, but this couples your DAO objects to the Spring framework, and you have to do some pretty ugly stuff to execute your named queries:
JpaMessageDao.java
package org.adrianwalker.maven.skeleton.spring.jpa; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceException; import javax.persistence.Query; import org.springframework.orm.jpa.JpaCallback; import org.springframework.orm.jpa.support.JpaDaoSupport; public final class JpaMessageDao extends JpaDaoSupport { public long count() { return (Long) getJpaTemplate().execute(new JpaCallback() { public Object doInJpa(final EntityManager em) throws PersistenceException { Query q = em.createNamedQuery("MessageEntity.count"); return q.getSingleResult(); } }); } public void create(final MessageEntity message) { getJpaTemplate().persist(message); } public MessageEntity read(final long id) { return getJpaTemplate().find(MessageEntity.class, id); } @SuppressWarnings("unchecked") public List<MessageEntity> read(final int firstResult, final int maxResults) { return (List<MessageEntity>) getJpaTemplate().execute(new JpaCallback() { public Object doInJpa(final EntityManager em) throws PersistenceException { Query q = em.createNamedQuery("MessageEntity.find"); q.setFirstResult(firstResult); q.setMaxResults(maxResults); return q.getResultList(); } }); } public MessageEntity update(final MessageEntity message) { return getJpaTemplate().merge(message); } public void delete(final MessageEntity message) { getJpaTemplate().remove(message); } }
A much neater alternative is to use a JPA style controller which gives you direct access to the EntityManager
. The @PersistenceContext
annotation is understood by Spring and your entity manager is injected into the controller class.
MessageJpaController.java
package org.adrianwalker.maven.skeleton.spring.jpa; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.PersistenceContext; public class MessageJpaController { @PersistenceContext(unitName = "messagePersistenceUnit") private EntityManager em; public long count() { Query q = em.createNamedQuery("MessageEntity.count"); return (Long) q.getSingleResult(); } public void create(MessageEntity messageEntity) { em.persist(messageEntity); } public MessageEntity read(final long id) { return em.find(MessageEntity.class, id); } @SuppressWarnings("unchecked") public List<MessageEntity> read(int firstResult, int maxResults) { Query q = em.createNamedQuery("MessageEntity.find"); q.setFirstResult(firstResult); q.setMaxResults(maxResults); return q.getResultList(); } public MessageEntity update(MessageEntity messageEntity) { return em.merge(messageEntity); } public void delete(final MessageEntity message) { em.remove(message); } }
Usage & Tests
And finally here are some JUnit 4 tests for both the DAO and Controller.
MessageTest.java
package org.adrianwalker.maven.skeleton.spring.jpa; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:context.xml" }) @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false) @Transactional public final class MessageTest { @Autowired private MessageJpaController messageJpaController; @Autowired private JpaMessageDao jpaMessageDao; @Test public void daoCreate() throws Exception { for (int i = 1; i <= 10; i++) { jpaMessageDao.create(new MessageEntity(String.format("Message %s", i))); } assertEquals(10, jpaMessageDao.count()); } @Test public void daoRead() throws Exception { List<MessageEntity> messages = jpaMessageDao.read(1, 2); assertNotNull(messages); assertEquals(2, messages.size()); } @Test public void daoUpdate() throws Exception { MessageEntity message = jpaMessageDao.read(1); assertNotNull(message); assertEquals("Message 1", message.getText()); message.setText("Message X"); message = jpaMessageDao.update(message); message = jpaMessageDao.read(1); assertEquals("Message X", message.getText()); } @Test public void daoDelete() throws Exception { long beforeCount = jpaMessageDao.count(); assertTrue(beforeCount == 10); for (int i = 1; i <= 10; i++) { MessageEntity message = jpaMessageDao.read(i); jpaMessageDao.delete(message); } long afterCount = jpaMessageDao.count(); assertTrue(afterCount == 0); } @Test public void controllerCreate() throws Exception { for (int i = 11; i <= 20; i++) { messageJpaController.create(new MessageEntity(String.format("Message %s", i))); } assertEquals(10, messageJpaController.count()); } @Test public void controllerRead() throws Exception { List<MessageEntity> messages = messageJpaController.read(1, 2); assertNotNull(messages); assertEquals(2, messages.size()); } @Test public void controllerUpdate() throws Exception { MessageEntity message = messageJpaController.read(11); assertNotNull(message); assertEquals("Message 11", message.getText()); message.setText("Message X"); message = messageJpaController.update(message); message = messageJpaController.read(11); assertEquals("Message X", message.getText()); } @Test public void controllerDelete() throws Exception { long beforeCount = messageJpaController.count(); assertTrue(beforeCount == 10); for (int i = 11; i <= 20; i++) { MessageEntity message = messageJpaController.read(i); messageJpaController.delete(message); } long afterCount = messageJpaController.count(); assertTrue(afterCount == 0); } }
Don't Believe The Hype
So basically, for me anyway, using Spring in conjunction with JPA gives you nothing above and beyond using JPA directly. By the the time you decide not to bother with JpaDaoSupport
, all that has been achieved is moving connection properties form persistence.xml
to context.xml
, which just creates more Spring configuration XML clutter.
Spring recognising the @PersistenceContext
is a nice touch for testing, but using constructor/setter injection to inject an EntityManager
into a JPA controller class is no big deal if you don't want to use spring.
Source Code
- Java Maven Project - maven-spring-jpa-skeleton.zip