Configuring a JBoss + Spring + JPA (Hibernate) + JTA web application

Updated version available: If you are looking for how JPA can be used in JBoss 5 and Spring 3.x versions, look at "Spring JPA web applications (JTA transactions, JBoss 5)". What you find below is a workaround to use JTA transactions in JBoss 4.x versions.

Here's how one might go about deploying a Spring application in JBoss (4.something) that uses JPA with Hibernate as the provider for persistence and JTA for transaction demarcation.

1. Define the Spring configuration file in the web.xml file

<context-param>
        <description>Spring configuration file</description>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

2. Define the Spring loader in the web.xml file

<listener>
        <description>Spring Loader</description>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

3. Define the persistence unit reference in the web.xml file (which in fact has no effect until the Servlet container supports Servlet spec 2.5):

<persistence-unit-ref>
        <description>
            Persistence unit for the bank application.
        </description>
        <persistence-unit-ref-name>persistence/BankAppPU</persistence-unit-ref-name>
        <persistence-unit-name>BankAppPU</persistence-unit-name>       
</persistence-unit-ref>

* Note that this is what enables "<jee:jndi-lookup>" which has been commented out in the below given Spring configuration file.

* For the above to work well, your web.xml should start like this (note the version 2.5):

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

4. Here's the persistence.xml file. Make the changes to the <jta-data-source> as you have defined in your system (for example in a file like JBOSS_HOME/server/default/deploy/bank-ds.xml - See JBOSS_HOME/docs/examples/jca/ for templates).

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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">
  <persistence-unit name="BankAppPU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:BankAppDS</jta-data-source>
    <properties>
      <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
      <property name="jboss.entity.manager.factory.jndi.name" value="java:/BankAppPU"/>
      <property name="hibernate.hbm2ddl.auto" value="update"/>
    </properties>
  </persistence-unit>
</persistence>

5. Here's a sample Spring configuration file (applicationContext.xml):

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">

    <!-- In a fully J5EE compatible environment, the following xml tag should work in accessing the EMF -->          
<!--
    <jee:jndi-lookup id="entityManagerFactory" jndi-name="java:/BankAppPU"/>
-->
  
    <!-- Hack for JBoss 4.something until full compliance is reached -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
      <property name="persistenceUnitName" value="BankAppPU"/>
    </bean>

    <!-- Let's access the JTA transaction manager of the application server -->
    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager"/>
        <property name="userTransactionName" value="UserTransaction"/>
    </bean>
   
    <!-- Let's define a DAO that uses the EMF -->
    <bean id="accountHolderDAO" class="bankapp.dao.AccountHolderDAO">
        <property name="emf" ref="entityManagerFactory"/>
    </bean>
   
    <!-- This is a service object that we want to make transactional.
         You will have an interface implemented (AccountManager) in the class.
    -->
    <bean id="accountManager" class="bankapp.AccountManagerImpl">
        <property name="accountHolderDAO" ref="accountHolderDAO"/>
    </bean>
   
   
    <!-- The transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only transactions -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*" read-only="false" />
        </tx:attributes>
    </tx:advice>
   
   
    <!-- ensure that the above transactional advice runs for execution
      of any operation defined by the AccountManager interface -->
    <aop:config>
        <aop:pointcut id="accountManagerOperation",
           expression="execution(* bankapp.AccountManager.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="accountManagerOperation"/>
    </aop:config>

</beans>

6. Here's the sample AccountManagerImpl:

public class AccountManagerImpl implements AccountManager {
   
    /** Creates a new instance of AccountManagerImpl */
    public AccountManagerImpl() {
    }

    private AccountHolderDAO accountHolderDAO;
   
    public AccountHolder createAccountHolder(AccountHolder accountHolder) throws BankException {
        return accountHolderDAO.create(accountHolder);
    }

    public AccountHolderDAO getAccountHolderDAO() {
        return accountHolderDAO;
    }

    public void setAccountHolderDAO(AccountHolderDAO accountHolderDAO) {
        this.accountHolderDAO = accountHolderDAO;
    } 
}

7. Here's the sample AccountHolderDAO:

public class AccountHolderDAO {
   
    /** Creates a new instance of AccountHolderDAO */
    public AccountHolderDAO() {
    }
   
    private EntityManagerFactory emf;

    public EntityManagerFactory getEmf() {
        return emf;
    }

    public void setEmf(EntityManagerFactory emf) {
        this.emf = emf;
    }
   
    public AccountHolder create(AccountHolder newAccountHolder) throws BankException {
        try {
           
            // JTA Transaction assumed to have been started by AccountManager (Spring tx advice)
            EntityManager em = emf.createEntityManager();
            //em.getTransaction().begin(); - Not required
            em.persist(newAccountHolder);
            //em.getTransaction().commit(); - Not required
            return newAccountHolder;
            // JTA Transaction will be completed by Spring tx advice
           
        } catch (Exception e) {
            throw new BankException("Account creation failed" + e.getMessage(), e);
        }
    } 
}

You will have some other code accessing the Spring bean "accountManager" and invoke the createAccountHolder() with the required parameters. Things should work well.