Monday, November 18, 2013

JPA configuration in Spring without persistence.xml

I'm just so lucky. Finally I got the JPA configure with Spring without the need of persistence.xml. I couldn't tell whether this is a correct configuration but it just works. The motivation on the integration of JPA is to seek for alternate resolution beside pure implementation on DAO with Hibernate

This is how I configure JPA in Spring.
 ...

 <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor">
 </bean>
 
 <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver">
  </property>
  <property name="url" value="jdbc:mysql://localhost:3306/test">
  </property>
  <property name="username" value="root">
  </property>
  <property name="password" value="root">
  </property>
 </bean>
 
 <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
  <property name="jpaVendorAdapter" ref="jpaVendorAdapter">
  </property>
  <property name="dataSource" ref="dataSource">
  </property>
  <property name="packagesToScan" value="org.huahsin">
  </property>
 </bean>
 
 <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" id="jpaVendorAdapter">
  <property name="databasePlatform" value="org.hibernate.dialect.MySQLInnoDBDialect">
  </property>
  <property name="showSql" value="true">
  </property>
 </bean>
The primary object in this configuration was the LocalContainerEntityManagerFactoryBean. According to the documentation, this is the most powerful way to set up a shared JPA EntityManagerFactory in Spring application context. I take 3 parameters in this configuration:
  1. dataSource - the bean that establish connection to database.
  2. jpaVendorAdapter - optional parameters in persistence.xml.
  3. packagesToScan - the packages where the entities is reside.
On DAO site, there are 2 ways to invoke a query. First one is to obtain an instance of EntityManagerFactory through @PersistenceUnit injection. From there obtain an instance of EntityManager to run a query.
@Repository
public class UserDao implements IUserDao {

 @PersistenceUnit
 private EntityManagerFactory emf;
 
 public User findByUsername(String username) {
  
  List<user> l = null;
  try {
   l = (List<user>) emf.createEntityManager().createQuery("from User").getResultList();
   
   // verify on the list of users
   for( User u : l ) {
    System.out.println(u.getUsername());
   }

   return null; // simply return NULL for testing purpose
  }
  finally {
   emf.close();
  }
 }
}
The second option is to obtain an instance of EntityManager through @PersistenceContext injection.
@Repository
public class UserDao implements IUserDao {

 @PersistenceContext
 private EntityManager em;
 
 public User findByUsername(String username) {
  
  List<User> l = null;
  try {
   l = (List<User>) em.createQuery("from User").getResultList();
   
   // verify on the list of users
   for( User u : l ) {
    System.out.println(u.getUsername());
   }

   return null; // simply return NULL for testing purpose
  }
  finally {
   emf.close();
  }
 }
}
Do not mess up with the injection. If I accidentally do this on EntityManagerFactory like this:

@PersistenceContext
private EntityManagerFactory emf;

This error would be seen when the bean is trigger during run-time:
Caused by: java.lang.IllegalStateException: Specified field type [interface javax.persistence.EntityManagerFactory] is incompatible with resource type [javax.persistence.EntityManager]
 at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.checkResourceType(InjectionMetadata.java:134)
 at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.<init>(PersistenceAnnotationBeanPostProcessor.java:620)
 at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findPersistenceMetadata(PersistenceAnnotationBeanPostProcessor.java:381)
 at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(PersistenceAnnotationBeanPostProcessor.java:322)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors(AbstractAutowireCapableBeanFactory.java:830)
 ... 27 more
Same to EntityManager, if I mistakenly do this:

@PersistenceUnit
private EntityManager em;

This will be the result during the run-time:
Caused by: java.lang.IllegalStateException: Specified field type [interface javax.persistence.EntityManager] is incompatible with resource type [javax.persistence.EntityManagerFactory]
 at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.checkResourceType(InjectionMetadata.java:134)
 at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.(PersistenceAnnotationBeanPostProcessor.java:620)
 at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findPersistenceMetadata(PersistenceAnnotationBeanPostProcessor.java:381)
 at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(PersistenceAnnotationBeanPostProcessor.java:322)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors(AbstractAutowireCapableBeanFactory.java:830)
 ... 27 more
There is a nice comment on this error why this shouldn't be done appear on this question in stackoverflow.com. The user claim that
An entity manager can only be injected in classes running inside a transaction. In other words, it can only be injected in a EJB. Other classe must use an EntityManagerFactory to create and destroy an EntityManager. - Andre Rodrigues

No comments: