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 :
- The spring.xml file, that drives the spring container.
- The persistence.xml file, that drives the JPA ORM layer
- 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