JPA with Hibernate and Spring: Fucking Cool(tm)

Yep. Really..

The True Way Of Developing Applications has been disturbed and that is damn good. For starters, please take a moment to read these articles in order:

Getting started with JPA and Spring JPA Annotations Guide Advanced JPA with spring

What will happen if you do:

  • No more need for hibernate mapping files; everything will work from annotations, persistent classes are automagically discovered, and greenfield projects can have the database automatically generated.
  • Controller interceptors can be substituted with Transaction annotations, which is much easier to understand and maintain. Spring will generate interceptors anyway but at least you do not have to be so aware of them.
  • No more need of JpaDaoSupport and JpaTemplate. They were a great commodity with JDBC and raw Hibernate, but with JPA it's cleaner if you do not use the support classes.
  • Do not forget to make your test classes extend AbstractJpaTests so that Spring can inject your attributes.

A full example of what we are talking about:

DAO: Just a regular JPA DAO.

public class BasicDAO {

   private EntityManager entityManager;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public List query(String queryString, final Object... params) {
        Query query = entityManager.createQuery(queryString);
        setParameters(query, params);
        return query.getResultList();
    }
}

Controller: Notice that the Transactional annotation allows tuning and can also be defined per method if needed.

@Transactional
public class BasicController implements IBasicController {

   private BasicDAO basicDAO;

   public List query(String queryString, Object... params) {
       return basicDAO.query(queryString, params);
   }

   public void setBasicDAO(BasicDAO basicDAO) {
      this.basicDAO = basicDAO;
   }

}

Controller interface, because Spring generates an aspect interceptor to implement the transactional behaviour:

public interface IBasicController {
   public abstract void query(String queryString, Object... params);
}

Test: Notice that I am using TestNG here and it does not by default call the setup() and tearDown() methods.

public class BasicControllerTest extends AbstractJpaTests {

   private IBasicController basicController;

   @Test
   public void testQuery() throws Exception {
      basicController.query("from Foo foo where foo.category.id=?", 1);
   }

   @BeforeTest
   public void launchSetup() throws Exception {
      setUp();
   }

   @AfterTest
   public void launchTearDown() throws Exception {
      tearDown();
   }

   protected String[] getConfigLocations() {
      return new String[] { "spring-config.xml" };
   }

   public void setBasicController(IBasicController basicController) {
      this.basicController = basicController;
   }

}

persistence.xml: This one is empty.

<?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
      http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
  version="1.0">

    <persistence-unit name="acme" transaction-type="RESOURCE_LOCAL"/>

</persistence>

Spring config file: we are using Hibernate as our JPA provider.

<?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.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd" 
  default-autowire="byName">

  <bean id="entityManagerFactory"
     class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
     <property name="jpaVendorAdapter">
        <bean
        class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
           <property name="showSql" value="true" />
           <property name="generateDdl" value="true" />
           <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" />
        </bean>
     </property>
  </bean>

  <bean id="dataSource"
     class="org.apache.commons.dbcp.BasicDataSource"
     destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:test/db/myDB" />
        <property name="username" value="sa" />
        <property name="password" value="" />
  </bean>

  <bean id="transactionManager"
     class="org.springframework.orm.jpa.JpaTransactionManager" />

  <tx:annotation-driven />

  <bean
     class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

  <bean id="basicDAO" class="com.acme.dao.BasicDAO" />

  <bean id="basicController" class="com.acme.service.BasicController"/>

</beans>

Warning: lots of magic here.

  • The default-autowire="byName" is the spring equivalent to Convention Over Configuration. Your configuration will shrink to half size because all properties will be automatically linked to a bean of the same name (such as entityManagerFactory, for example).
  • The testcase will inject by type all attributes with a setter method defined. This can be tuned with AbstractJpaTests.setAutowireMode().
  • I have not specified a location for the persistence.xml file: by default, it will look for META-INF/persistence.xml in the classpath.
  • With these settings the database is going to be created at startup, which is an easy way of generating your first DDL. Of course, this is only acceptable during beta stages of the project.

Update: Per popular request, a complete war with everything included in this post plus some paging examples and automatic JPA validation has been included in the Loom demo application (download here). Check the demo war and the included sample source files. You may just skip the part about the web framework in case you prefer stripes, struts or (god forbid) JSF. Enjoy!