Thursday, 28 June 2007

The Hans Gruber effect - Using Spring and Hibernate JPA

At the end of the movie Die Hard John McClane (Bruce Willis), releases the watch from Holly's wrist which results in Hans Gruber (Alan Rickman) falling to his death. Hans was hanging out of a window in a huge high rise, holding onto the wrist of Holly. The iconic scene has the view from their perpective seeing Hans flapping his arms moving further and further away from them as he falls to the ground. In some ways I felt like Hans flapping my arms trying to make Hibernate JPA and Spring work, whilst gravity was pulling me closer to my death ...

Well my work colleague, Dave, first put me on the path to enlightenment. He references Ignacio's experience or rather what he did to make the thing work. However, in all the references I flapped more and more as they all do it a little differently. Also Ignacio uses Spring auto-wiring and recently in a Spring course by Interface 21, the presenter, reckoned that auto-wiring should not really be used.

Maybe the biggest factor in my Hans Gruber impersonation is that at times everyone else makes it look so easy while I feel like a dufus. But I did eventually succeed.

Here's how it worked for me:

I pretty much based everything I did on Getting Started with JPA in Spring 2.0, however I couldn't get it to work properly. I also used Hibernate JPA and therefore had to make a few changes. I will list these changes below.

Issue 1: Gather all the required Jar's

This is a bit of a problem as many of the blogs I read didn't give a list of Jar's required. I downloaded a version of:

* Spring 2.04
* Hibernate 3.2
* Hibernate Entity Manager 3.3.1

From these downloads a I used the following jar files. Still not sure if they are all needed but here is the list:

antlr-2.7.6.jar
dom4j-1.6.1.jar
javassist.jar
asm-attrs.jar
ejb3-persistence.jar
jboss-archive-browsing.jar
asm.jar
hibernate-annotations.jar
jta.jar
cglib-2.1.3.jar
hibernate-commons-annotations.jar
log4j-1.2.14.jar
classes12.jar (I am using Oracle DB)
hibernate-entitymanager.jar
spring-2.0.4.jar
commons-collections-2.1.1.jar
hibernate-validator.jar
commons-logging-1.0.4.jar
hibernate3.jar

Issue 2: Spring config file

There were a few issues with setting up the config file.

Note the following:

  • Include the annotations for transactions, this becomes relevant when performing transactions and a solution for Lazy fetches.
  • I couldn't get it to wotk with ContainerEntityManagerFactoryBean so I used LocalContainerEntityManagerFactoryBean


<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">

<!-- Put this here if you want to use transactions -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- This comes from the blog
http://blog.interface21.com/main/2006/05/30/getting-started-with-jpa-in-spring-20/ -->
<bean id="restaurantDao" class="blog.jpa.dao.JpaRestaurantDao">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<!-- I used Oracle just change the dialect below to match your db -->
<!-- I couldn't get the ContainerEntityManagerFactoryBean to work
so I used LocalContainerEntityManagerFactoryBean -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="false" />
<property name="databasePlatform" value="org.hibernate.dialect.Oracle9Dialect" />
</bean>
</property>
</bean>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@xxx:1521:xxx" />
<property name="username" value="bandaid" />
<property name="password" value="bandaid" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
<property name="dataSource" ref="dataSource" />
</bean>


</beans>


Issue 3: Lazy vs Eager Fetching

All was happy until I started playing around with lazy fetching. The problem was that I kept getting a session ending error after the initial read. This appears to be a Spring issue. Spring closes the database connection and you try to do the lazy fetch it throws an error that basically means the database connection doe snot exist. So after reading many blogs, pulling my hair out and crying myself to sleep at night, I stumbled onto the following blog Hibernate Lazy Loading.

I discovered a few work arounds for Spring:

  1. Don't make any Lazy calls and just call the collection manually. I didn't like that option.
  2. Make everything Eager. Not a good one either.
  3. Implement the OpenSessionInViewFilter in the Spring config. I read a few more blogs, referenced all the spring books I had and still couldn't get the thing to work. It does seem to work but I couldn't figure it out.
  4. Use the Spring @Transaction annotation for the method or class. That's why I declared the annotations in the above spring config file.

Outcome

I am very happy. Smiles all round. After many days impersonating Hans Gruber, I finally got the thing to work. It's really simple and easy. I will tell my friends about it.

3 comments:

jo said...

Am I the only one that ever reads your blog? P.S. Haven't found you a t-shirt yet.

David Levy said...

Nope, not the only one who reads this blog. Nice to hear you got it working :)

Here's the way I got over the Fetching problem. It's a Hibernate specific workaround, but seems to work well:

@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.EAGER)
@Fetch(FetchMode.SUBSELECT)
private List<CrawlFilter> crawlFilters;


The '@Fetch(FetchMode.SUBSELECT)' is Hibernate specific and not in the JPA specification.

Unknown said...

Hi Hans :))

The post you are referencing is quite old, before common practice started recommending not to autowire. Anyway, I do not autowire with real big projects, but the reason most commonly aduced (clarity of configuration) would also leave current practices such as configuration by annotation and JavaConfig out of question. Go figure.

Cheers