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!