So, I’m using Hibernate 3.2 with annotations for the persistence layer of my investment application. I have some generated values in certain domain objects that are fairly expensive to compute (such as the internal rate of return of an account), and so I don’t want them recomputed every time the getter is called. EJB3 defines a @PostLoad annotation which specifies a method to be invoked after a domain object is loaded from the store. Perfect. The only problem is that it doesn’t work using SessionFactory-based configuration, you have to use an EntityManager-based configuration or the post-load method never gets called (i.e., it silently fails – nice caveat of annotations, that).
Anyway, I’ve converted my Spring configuration to use JPA, which is pretty nice since it (almost) makes the configuration Hibernate-agnostic (I say “almost” because you still have to specify the jpaVendorAdapter on the EntityManagerFactory).
Here’s my persistence.xml file, which I keep down to the bare minimum by configuring the data source at the Spring level (so I can use different Spring contexts and data sources for testing):
<?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="Invest"> <!-- Uncomment this block to specify the data source at the JPA level rather than the Spring level <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" /> <property name="hibernate.connection.url" value="jdbc:mysql://localhost/invest" /> <property name="hibernate.connection.username" value="root" /> <property name="hibernate.connection.password" value="" /> </properties> --> </persistence-unit> </persistence>
BTW, the persistence.xml file has to be located in the META-INF directory of one of the classpath entries. I use an Ant target copy-resources to copy this to build/classes/META-INF from my conf directory.
Here’s an exerpt from my Spring context file:
<?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"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost/invest" /> <property name="username" value="root" /> <property name="password" value="" /> </bean> <bean id="securityDao" class="com.jolai.invest.dao.jpa.JpaSecurityDao"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean id="accountDao" class="com.jolai.invest.dao.jpa.JpaAccountDao"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean id="portfolioDao" class="com.jolai.invest.dao.jpa.JpaPortfolioDao"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitName" value="Invest" /> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> <property name="dataSource" ref="dataSource" /> </bean> </beans>
Note that DAOs should extend JpaDaoSupport instead of HibernateDaoSupport, and unit tests should extend AbstractTransactionalSpringContextTests instead of AbstractTransactionalDataSourceSpringContextTests.
Everything works, but I did get bit by Hibernate bug EJB-244, which will cause your unit tests to fail with an “unknown entity” exception if you’re running them from a directory whose full path contains a space (like “My Documents” under Windows). Arrgh. I used the junction utility to link /home/tjones to my “My Documents” directory.
I like this approach because JPA should be a bit more standards-compliant and allow me to swap out Hibernate for TopLink or iBatis with no code changes (in theory).
Here are some links which I found helpful during this exercise: