Sunday, November 26, 2006

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!

25 comments:

  1. Great post!
    Could You put up this working example source code somewhere in an archive file?

    ReplyDelete
  2. I am still waiting for this RFE in the stripes JIRA to be approved. When/if accepted, I plan to contribute a full working example (including the view validating input using JPA annotations) to a project that already exists (Appfuse and Project able comes to mind)

    ReplyDelete
  3. Hi,

    Nice post, helped me finding the missing pieces!

    2 remarks:
    1. Using org.springframework.orm.jpa.support.JpaDaoSupport
    makes life even easier
    2. @BeforeTest and @AfterTest should be @BeforeMethod and @AfterMethod because in the JUnit universe the setup and teardown methods are called before and after each test method. Of course this only applies if you have more then one test method in your test class.

    ReplyDelete
  4. Hi Roel,

    Thanks for the TestNG bit. I Overlooked that :)

    About JpaDaoSupport, I found that avoiding it results in simpler DAO methods (in the concrete case of JPA, of course).

    ReplyDelete
  5. I followed the as above. But I am getting following exception. Any idea why this error?
    log4j:WARN No appenders could be found for logger
    Exception in thread "main" org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.UnsupportedOperationException: Not supported by BasicDataSource
    Caused by: java.lang.UnsupportedOperationException: Not supported by BasicDataSource
    at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:899)
    at org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider.getConnection(InjectedDataSourceConnectionProvider.java:44)
    at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:423)
    at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
    at org.hibernate.jdbc.JDBCContext.connection(JDBCContext.java:119)
    at org.hibernate.transaction.JDBCTransaction.begin(JDBCTransaction.java:57)
    at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1326)
    at org.hibernate.ejb.TransactionImpl.begin(TransactionImpl.java:38)
    at org.springframework.orm.jpa.DefaultJpaDialect.beginTransaction(DefaultJpaDialect.java:69)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:48)
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:326)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:350)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:262)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:102)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:630)
    at com.ibswings.reminder.service.ReminderServiceImpl$$EnhancerByCGLIB$$a32e6e63.retrieveReminder(<generated>)
    at com.ibswings.reminder.ReminderTest.testRetrieveReminder(ReminderTest.java:92)
    at com.ibswings.reminder.ReminderTest.go(ReminderTest.java:42)
    at com.ibswings.reminder.ReminderTest.main(ReminderTest.java:37)

    ReplyDelete
  6. Hi,
    Just wondering, does JPA support DDL operations. If so, can you please explain or suggest resources that have additional info.
    Thanks.

    ReplyDelete
  7. Srini, JPA will create your database structure if that's what you are asking for. In the Loom demo application you will find a working example (check the spring config file).

    ReplyDelete
  8. I'm hitting a null pointer error when I try and run a query.

    The entityManager isn't being set for the BasicDAO bean.

    I can't see where it's defined anywhere so I'm not entirely suprised, but I'm not sure how to fix it.

    ReplyDelete
  9. Been looking all over for a best practices example of Spring/Hibernate using JPA. I noticed this was first created in '06. Is this example still considered a good one to follow? Have some newer things come out within Spring/Hibernate that would change the way the loom app was written if starting over? Thanks.

    ReplyDelete
  10. Well, this has changed since this post was written like a life ago:

    * Autowire is not considered good practice, and in fact never was. When I wrote this it was considered a nice experiment.
    * Annotation-based wiring like the one described on this post is considered a good practice. The tests could be rewritten using Junit 4 or TestNG annotations using the new spring-tests support.
    * I had to choose between configuring a JPA container by hand using spring (like described here) or connecting spring to any other EJB 3 container. I used the former to be able to deploy the Loom demo on any application server, and this far I have successfully tested Geronimo, Glassfish, Weblogic, Tomcat and Jetty. Your case may be different.
    * If you are interested, there are additional examples in the spring sources tree (search for persistence.xml or EntityManager and start looking from there).

    The Loom demo is being kept (more or less) up to date with what I think is the easiest way to deploy a JPA application. I am right now preparing a scaffolding library to setup a web environment (including JPA), but that may take a couple of weeks.

    ReplyDelete
  11. Thanks Ignacio, for your comment. What I'm finding extremely frustrating is figuring out what is considered the "best practice" way of doing things, in regard to using Spring and JPA (with Hibernate underneath and preferably all building with Maven.) Sadly, as I start exploring, things are done differently in so many places. For example, there is Appfuse and Appfuse-Light, but even within those two projects they do things differently. The Spring PetsClinic seems ok, but it doesn't even use annotations on the models, but that's not a big deal to change so I'll probably start with that. I guess I'll have to just put together the best approach I can from looking at all these different tutorials. Thanks again for your comments. Good to know about the "autowire" functionality since I've seen that used a lot in some of the tutorials I've come across.

    ReplyDelete
  12. Rick, if you could contribute your conclusions it would be great. I am open to other ways of doing things :)

    ReplyDelete
  13. This post is for those want to use (Spring2 JPA)
    I am using link http://java.sys-con.com/node/366275?page=1


    Spring2 JPA
    I haven;t use toplink...
    (i haven;t use loadtimeweaver: it's giving problem)
    I am using hibernate instead of toplink with jdbc connection using mysql instead of oracle


    if you get exception
    java.lang.NoClassDefFoundError:
    then you Need


    (asm.jar)(org.objectweb.asm.Type)

    (cglib)(net.sf.cglib.proxy.CallbackFilter)

    (commons-collections.jar)(org.apache.commons.collections.SequencedHashMap)

    (commons-logging.jar)(org/apache/commons/logging/Log)

    (dom4j.jar)(org.dom4j.DocumentException)

    (ejb3-persistence.jar)(javax.persistence.EntityManager)(need sharedEntityManager)

    (hibernate3.jar)(org.hibernate.dialect.MySQLDialect)

    (hibernate-annotations.jar)(org/hibernate/cfg/AnnotationConfiguration)

    (hibernate-commons-annotations.jar)(org/hibernate/annotations/common/reflection/ReflectionManager)

    (hibernate-entitymanager.jar)(org.hibernate.ejb.HibernatePersistence)

    (javassist.jar)(javassist.bytecode.ClassFile)

    (jboss-archive-browsing.jar)(org.jboss.util.file.ArchiveBrowser$Filter)

    (jdbc2_0-stdext.jar)(org/hibernate/dialect/MySQLDialect)

    (jta.jar)(javax.transaction.SystemException)

    (junit4.jar)(use for asserts,extending AbstractJpaTests class)

    (mysql-connector-java-3.1.6-bin.jar)(PropertyAccessException 1: + java.lang.IllegalStateException: Could not load JDBC driver class [com.mysql.jdbc.Driver])

    (Spring.jar)(As using spring2.5 framework)

    (spring-mock.jar)(As going to use test cases dependent on spring, so as to get less configuration)

    ReplyDelete
  14. I am with rick_r when he says that finding out what is best practice is hard. I am just looking for a simple Spring+JPA set-up to port the ZK presentation framework's "Todo" sample application onto. I will take a look at the loom spring configuration files. Thanks for your great post and your commitment to cool code.

    ReplyDelete
  15. Yes the loom configuration and it's BasicDao class was indeed a great place to start. Loom is clearly a sophisticated keel or appfuse like environment. My sample quick app is more of a minimal set-up to demo getting going with maven, jpa, spring, hibernate and zk. So I just took BasicDao and the spring config and slapped on the front-end of the original zk+jdbc sample app. The code is here on sourceforge for svn check out (for the moment, it might down a folder into trunk).
    https://zkforge.svn.sourceforge.net/svnroot/zkforge/zktodo2/
    thanks again

    ReplyDelete
  16. org.loom.demo.controller.DemoService is missing from the source

    ReplyDelete
  17. Hi Kelvin,

    You are not really missing much, the whole class has like 60 lines and half of it are comments. You can check it here.

    In the meanwhile, I will check that it gets included in the release. Thanks for the heads-up!

    ReplyDelete
  18. Awesome post. I've looked all over the place and this is exactly what I needed!

    ReplyDelete
  19. Great post! I'd been looking around for hours until I found your tip:

    default-autowire="byType"

    This made autowiring work! Many thanks!

    ReplyDelete
  20. Nice, but not working at all :)

    Error creating bean with name 'transactionManager'... Property 'entityManagerFactory' is required - which you do not provide.

    ReplyDelete
  21. Anonymous, entityManagerFactory is the FactoryBean providing EntityManager instances. If you are not using autowire, just inject it explicitly.

    ReplyDelete
  22. cool! i also wanna share this very spoon fed version of Spring + Hibernate + JPA combo

    http://www.adobocode.com/spring/spring-with-hibernate-annotations

    ReplyDelete
  23. I also like annotations in some extent, and always favor spring.

    ReplyDelete
  24. I was stuck with a stupid connection error for about a day before stumbling upon this post and it just worked when I followed your configuration. Magic! thanks.

    ReplyDelete

Something on your mind?

Note: Only a member of this blog may post a comment.