Tuesday, 18 May 2010

JBoss 4.2.x Spring 3 JPA Hibernate Tutorial


After spending many hours searching the web, trying to find the most efficient way to use Spring, JPA and Hibenate for several projects we concluded to the configuration that will be presented below. Integrating Spring with JPA and Hibernate consists of several steps :
  • Configuration of the Spring container
  • Configuration of the JPA ORM layer
  • Configuration of Hibernate Second lvl cache (if needed)

Keep in mind that we assume we are operating inside a J2EE container as far as data-sources are concerned, so we are going to lookup an existing data-source from JNDI and use it. The files that we are going to configure are those described below :
  1. The spring.xml file, that drives the spring container.
  2. The persistence.xml file, that drives the JPA ORM layer
  3. The second lvl cache file e.g. treecache.xml for the JBoss TreeCache provider (if needed)
An example spring.xml file is presented below :
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:task="http://www.springframework.org/schema/task"
 xsi:schemaLocation="
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
   http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"
>

 <context:component-scan base-package="com.mycomp.mypackage" />

 <task:annotation-driven executor="myExecutor" scheduler="myScheduler" />

 <task:executor id="myExecutor" pool-size="5" />

 <task:scheduler id="myScheduler" pool-size="10" />

 <tx:annotation-driven />

 <bean id="entityManagerFactory"
  class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitName" value="MyPersistenceUnit" />
 </bean>

 <bean id="transactionManager"
  class="org.springframework.transaction.jta.JtaTransactionManager">
  <property name="transactionManagerName" value="java:/TransactionManager" />
  <property name="userTransactionName" value="UserTransaction" />
 </bean>

</beans>
A few things to notice here
  • Change the base-package attribute of the context:component-scan tag to whatever is the base package of your project so as to be scanned for Spring components (services, DAOs etc)
  • Change the value attribute of entityManagerFactory bean persistentUnitName property to the name of your persistent unit as dictated in the persistence.xml file
An example persistence.xml file is presented below :
<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="MyPersistenceUnit" transaction-type="JTA">

  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <jta-data-source>java:/MyDataSource</jta-data-source>
 
  <properties>
   <property name="hibernate.dialect"  
     value="org.hibernate.dialect.HSQLDialect"/>
   <property name="hibernate.hbm2ddl.auto" value="update"/>
   <property name="hibernate.show_sql" value="false"/>
   <property name="hibernate.transaction.manager_lookup_class"  
     value="org.hibernate.transaction.JBossTransactionManagerLookup"/>

   <!-- Uncomment below to use Hibernate second lvl cache -->
   <!--
   <property name="hibernate.cache.provider_class" 
     value="org.hibernate.cache.TreeCacheProvider"/>
   <property name="hibernate.treecache.mbean.object_name" 
     value="jboss.cache:service=TreeCache"/>
   <property name="hibernate.cache.use_second_level_cache" 
     value="true"/>
   <property name="hibernate.cache.use_query_cache" 
     value="true"/>
   <property name="hibernate.cache.use_structured_entries" 
     value="true"/>
   -->
  </properties>

 </persistence-unit>

</persistence>
A few things to notice here :
  • The persistence unit name, here MyPersistenceUnit, must match the value in the spring.xml file
  • The transaction type can have JTA or LOCAL values, our assumption is that we are deploying inside a JTA enabled container so we can make use of global transactions (JTA), in any other case you should use the LOCAL value
  • As mentioned above we assume that we are deploying in a J2EE container with data-sources declared in separate files and located in JNDI, so we dictate JPA to use the data-source found under the specific JNDI name, you should change this value according to your data-source JNDI name
  • We dictate hibernate to use the Hypersonic dialect, you should change this value according to your data-source, e.g. for MySQL this value should be org.hibernate.dialect.MySQLDialect (for myISAM engine) or org.hibernate.dialect.MySQLInnoDBDialect (for InnoDB engine)
  • We dictate hibernate to lookup our JTA transaction manager, e.g if we are deploying inside JBoss we use the specified value, you should change this value according to your environment
  • If you want to enable Hibernate second lvl cache just uncomment the specified configuration directives
An example second lvl cache (JBoss tree cache - treecache.xml) file is presented below :
<server>

 <classpath codebase="./lib" archives="jboss-cache.jar, jgroups.jar" />

 <mbean code="org.jboss.cache.TreeCache" name="jboss.cache:service=TreeCache">
  <depends>jboss:service=Naming</depends>
  <depends>jboss:service=TransactionManager</depends>
  <attribute name="TransactionManagerLookupClass">
   org.jboss.cache.JBossTransactionManagerLookup</attribute>
  <attribute name="IsolationLevel">REPEATABLE_READ</attribute>
  <attribute name="CacheMode">LOCAL</attribute>
  <attribute name="UseReplQueue">false</attribute>
  <attribute name="ReplQueueInterval">0</attribute>
  <attribute name="ReplQueueMaxElements">0</attribute>
  <attribute name="ClusterName">TreeCache-Cluster</attribute>
  <attribute name="ClusterConfig">
   <config>
    <UDP mcast_addr="228.1.2.3" mcast_port="48866" ip_ttl="64"
     ip_mcast="true" mcast_send_buf_size="150000" mcast_recv_buf_size="80000"
     ucast_send_buf_size="150000" ucast_recv_buf_size="80000" loopback="false" />
    <PING timeout="2000" num_initial_members="3" up_thread="false"
     down_thread="false" />
    <MERGE2 min_interval="10000" max_interval="20000" />
    <FD_SOCK />
    <VERIFY_SUSPECT timeout="1500" up_thread="false"
     down_thread="false" />
    <pbcast.NAKACK gc_lag="50" retransmit_timeout="600,1200,2400,4800"
     max_xmit_size="8192" up_thread="false" down_thread="false" />
    <UNICAST timeout="600,1200,2400" window_size="100"
     min_threshold="10" down_thread="false" />
    <pbcast.STABLE desired_avg_gossip="20000" up_thread="false"
     down_thread="false" />
    <FRAG frag_size="8192" down_thread="false" up_thread="false" />
    <pbcast.GMS join_timeout="5000" join_retry_timeout="2000"
     shun="true" print_local_addr="true" />
    <pbcast.STATE_TRANSFER up_thread="true"
     down_thread="true" />
   </config>
  </attribute>
  <attribute name="FetchInMemoryState">true</attribute>
  <attribute name="InitialStateRetrievalTimeout">20000</attribute>
  <attribute name="SyncReplTimeout">20000</attribute>
  <attribute name="LockAcquisitionTimeout">15000</attribute>
  <attribute name="EvictionPolicyClass"></attribute>
  <attribute name="UseMarshalling">false</attribute>
 </mbean>

</server>
A few things to notice here
  • Update the path for the classloader to locate the two required files (jboss-cache.jar, jgroups.jar) for JBoss tree cache to work
  • This file configures the tree cache as a JBoss MBean service so it must be deployed in a JBoss application server, other configuration types are out of the scope of this tutorial and will not be discussed further. For configuring JBoss tree cache in another environment please refer to the appropriate documentation
That's it, now let me present a sample DTO and DAO based on the configuration described above :

First the DTO class (EmployeeDTO)
package com.mycomp.myproject.dto;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEE")
public class EmployeeDTO implements java.io.Serializable {
    
    private static final long serialVersionUID = 7440297955003302414L;

    @Id
    @Column(name="employee_id")
    private long employeeId;
    
    @Column(name="employee_name", nullable = false, length=30)
    private String employeeName;
    
    @Column(name="employee_surname", nullable = false, length=30)
    private String employeeSurname;
    
    @Column(name="job", length=50)
    private String job;
        
    public EmployeeDTO() {
    }

    public EmployeeDTO(int employeeId) {
        this.employeeId = employeeId;        
    }

    public EmployeeDTO(long employeeId, String employeeName, String employeeSurname,
            String job) {
        this.employeeId = employeeId;
        this.employeeName = employeeName;
        this.employeeSurname = employeeSurname;
        this.job = job;
    }

    public long getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(long employeeId) {
        this.employeeId = employeeId;
    }

    public String getEmployeeName() {
        return employeeName;
    }

    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }

    public String getEmployeeSurname() {
        return employeeSurname;
    }

    public void setEmployeeSurname(String employeeSurname) {
        this.employeeSurname = employeeSurname;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

}
Following is the DAO class for accessing Employee data (EmployeeDTO)
package com.mycomp.myproject.dao;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManagerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.mycomp.myproject.dto.EmployeeDTO;

@Repository("employeeDAO")
public class EmployeeDAO extends JpaDAO<Long, EmployeeDTO> {
    
    @Autowired
    EntityManagerFactory entityManagerFactory;
    
    @PostConstruct
    public void init() {
        super.setEntityManagerFactory(entityManagerFactory);
    }
    
}
As you can see the EmployeeDAO class extends a basic DAO class (JpaDao). The EmployeeDAO class can contain specific queries concerning the EmployeeDTO object, but all CRUD operations can be handled from the basic DAO class (JpaDao) presented below :
package com.mycomp.myproject.dao; 

import java.lang.reflect.ParameterizedType; 
import java.util.List; 

import javax.persistence.EntityManager; 
import javax.persistence.PersistenceException; 
import javax.persistence.Query; 

import org.springframework.orm.jpa.JpaCallback; 
import org.springframework.orm.jpa.support.JpaDaoSupport; 

public abstract class JpaDAO<K, E> extends JpaDaoSupport { 
    protected Class<E> entityClass; 

    @SuppressWarnings("unchecked") 
    public JpaDAO() { 
        ParameterizedType genericSuperclass = (ParameterizedType) getClass() 
                .getGenericSuperclass(); 
        this.entityClass = (Class<E>) genericSuperclass 
                .getActualTypeArguments()[1]; 
    } 

    public void persist(E entity) { 
        getJpaTemplate().persist(entity); 
    } 

    public void remove(E entity) { 
        getJpaTemplate().remove(entity); 
    } 
    
    public E merge(E entity) { 
        return getJpaTemplate().merge(entity); 
    } 
    
    public void refresh(E entity) { 
        getJpaTemplate().refresh(entity); 
    } 

    public E findById(K id) { 
        return getJpaTemplate().find(entityClass, id); 
    } 
    
    public E flush(E entity) { 
        getJpaTemplate().flush(); 
        return entity; 
    } 
    
    @SuppressWarnings("unchecked") 
    public List<E> findAll() { 
        Object res = getJpaTemplate().execute(new JpaCallback() { 

            public Object doInJpa(EntityManager em) throws PersistenceException { 
                Query q = em.createQuery("SELECT h FROM " + 
                        entityClass.getName() + " h"); 
                return q.getResultList(); 
            } 
            
        }); 
        
        return (List<E>) res; 
    } 

    @SuppressWarnings("unchecked") 
    public Integer removeAll() { 
        return (Integer) getJpaTemplate().execute(new JpaCallback() { 

            public Object doInJpa(EntityManager em) throws PersistenceException { 
                Query q = em.createQuery("DELETE FROM " + 
                        entityClass.getName() + " h"); 
                return q.executeUpdate(); 
            } 
            
        }); 
    }
    
}
This tutorial concludes, with the second part, where we are going to discuss about Spring service creation here

Justin



9 comments:

  1. Hi friend

    I was looking at your tutorial, which is very interesting but something I miss here is how you configure your project and what are the version of your hibernate dependencies. Did they conflict if you use jboss 5.1, currently I'm using this version and I couldn't find a way to get it work :-(

    Thanks

    ReplyDelete
  2. Hello friend

    This tutorial is for JBoss 4.2.x - 4.3, JBoss 5.1 has a newer version of hibernate but you should experience conflicts only if you enable second level cache. Additionally since JBoss from version 5 and on is a fully J2EE compliant application server you should configure spring to fetch the entityManagerFactory from JNDI, nevertheless the above configuration should also work.

    Best Regards

    Justin

    ReplyDelete
  3. Hello,

    Did you try to make JBoss 5 work with Hibernate 3.5. Here the there is a clash if you try to use JPA since Hibernate implements JPA2 and the version present in the server libraries are JPA1 implementations. I don't remember the exact error which you get, but I can say the application never deploys. We don't get this error if just use session factories and not the EntityManager.

    Regards
    Praveen

    ReplyDelete
  4. Thank you very much Praveen and Justin for all your feedback , I will let you know if I can have JBoss 5.1 works with JPA and Spring :-D

    ReplyDelete
  5. Hi,
    Very good article.Especially note after codes.
    Khosro.

    ReplyDelete
  6. Hi!

    Juste a little question. In the JpaDAO Class, how to detach an entity?

    Thankx in advance.

    Waldo

    ReplyDelete
  7. Hello Waldo,

    The merge function detaches the provided instance. Merge creates a new instance of your entity, copies the state from the supplied entity, and makes the new copy managed. The instance you pass in will not be managed (any changes you make will not be part of the transaction - unless you call merge again)

    BRs

    ReplyDelete
  8. you will go to the eclipse then select debug>debug configuration>web application > GWTSpring>server >check the chekbox "automatically select an unused port">
    then your pb is resolved .tanmirt nun and azul flawn ;-)

    ReplyDelete
  9. Great Post.
    Another Sample Application to Integrate Spring, Hibernate and JBoss
    http://eiconsulting.blogspot.com/2011/09/hibernat-spring-and-jboss.html

    ReplyDelete

Related Posts Plugin for WordPress, Blogger...