Spring JPA web applications (JTA transactions, JBoss 5)

With Spring 3.x, you can develop JPA applications very easily. Here you find guidelines in creating such an application with the following features:

  1. Use JTA for transaction management (with very little modifications you can use resource local transactions as well).
  2. Make sure the same JPA EntityManager instance is used across multiple DAOs so that a single business method can use multiple DAO method calls within the same transactional context.
  3. Make sure the same EntityManager instance is available for multiple DAOs in a thread safe manner.

Environment used is given below. You may use other versions of software with minor changes (for example Spring 3.0.1 and so on).

  1. Spring 3.0.0
  2. JBoss 5.1.0 (JDK 1.6 version)
  3. JDK 1.6.0.11
  4. Hibernate (the one packaged with JBoss 5.1.0)
  5. Database used: MySQL 5.1.37
  6. Development environment: Eclipse Galileo

Here are the steps:

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:

	
<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>

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>
      <!-- Auto-detect entity classes -->
      <property name="hibernate.archive.autodetection" value="class, hbm"/>

      <!-- Print sql executed - useful for debugging -->
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>

      <property name="hibernate.transaction.manager_lookup_class" 
          value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>

      <property name="hibernate.hbm2ddl.auto" value="create"/>
    </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:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- JPAAccountDAO has JPA annotations to access EntityManager -->
    <context:annotation-config/>

    <!-- Access the EntityManagerFactory like this - if ever you need -->
    <jee:jndi-lookup id="emf" jndi-name="persistence/BankAppPU"/>

    <!-- Here's a DAO that we will be using -->
    <bean id="accountDAO" class="bank.dao.jpa.JPAAccountDAO"/>

    <!-- Will be using the DAO in a business logic class AccountManager -->
    <bean id="accountManager" class="bank.AccountManagerImpl">
        <property name="accountDAO" ref="accountDAO"></property>
    </bean>

    <!-- Configure Transaction Support - Access the JTA transaction manager -->
    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager">
          <property name="transactionManagerName" value="java:/TransactionManager"/>
        <property name="userTransactionName" value="UserTransaction"/>
    </bean>

    <!-- Use Spring AOP capabilities to manage transactions -->
    <aop:config>
      <aop:pointcut id="accountTransactions" 
               expression="execution(* bank.AccountManager.*(..))"/>
      <aop:advisor pointcut-ref="accountTransactions" advice-ref="txAdvice" />
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
      <tx:attributes>
        <tx:method name="create" propagation="REQUIRED"/>
        <tx:method name="withdraw*" propagation="REQUIRED"/>
        <tx:method name="deposit*" propagation="REQUIRED"/>
        <tx:method name="chargeForLowBalance*" propagation="REQUIRED"/>
        <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
      </tx:attributes>
    </tx:advice>
</beans>

6. Here's the sample AccountManagerImpl:

	
package bank;

import java.util.Iterator;
import java.util.List;

import bank.dao.AccountDAO;

public class AccountManagerImpl implements AccountManager {

    private AccountDAO accountDAO;
    public AccountDAO getAccountDAO() {
        return accountDAO;
    }
    public void setAccountDAO(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }

    @Override
    public void chargeForLowBalance(double minimumBalance, double amount) {
        List<Account> accounts = accountDAO.findAccountsWithLowBalance(minimumBalance);
        for (Iterator<Account> iterator = accounts.iterator(); iterator.hasNext();) {
            Account account = (Account) iterator.next();
            // Check if the balance will go beyond 0. If yes, set the balance to 0
            account.setBalance(account.getBalance() - amount);
            accountDAO.update(account);
        }
    }

    @Override
    public Account create() {
        return accountDAO.createAccount();
    }

    @Override
    public void delete(int accountNumber) {
        accountDAO.delete(accountNumber);
    }

    @Override
    public void deposit(Account account, double amount) {
        //Account a = accountDAO.getAccount(accountNumber);
        account.setBalance(account.getBalance() + amount);
        accountDAO.update(account);
    }

    public void deposit(int accountNumber, double amount) {
        Account account = accountDAO.getAccount(accountNumber);
        deposit(account, amount);
    }
    

    @Override
    public void withdraw(int accountNumber, double amount) {
        Account account = accountDAO.getAccount(accountNumber);
        withdraw(account, amount);
    }    
    
    @Override
    public void withdraw(Account account, double amount) {
        if (account.getBalance() >= amount) {
            account.setBalance(account.getBalance() - amount);
            accountDAO.update(account);
        }   
        // Throw an exception (InsufficientBalanceException)?
    }
}

7.  Here's the JPAAccountDAO class:

package bank.dao.jpa;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import bank.Account;
import bank.dao.AccountDAO;

public class JPAAccountDAO implements AccountDAO {

    @PersistenceContext
    private EntityManager em;

    
    @Override
    public Account createAccount() {
        Account a = new Account();
        em.persist(a);
        return a;
    }

    @Override
    public void delete(int accountNumber) {
        em.remove(em.find(Account.class, accountNumber));
    }

    @Override
    public List<Account> findAccountsWithLowBalance(double lessThanAmount) {
        return em.createQuery("select a from Account a where a.balance < :amount")
                .setParameter("amount", lessThanAmount).getResultList();
    }

    @Override
    public List<Account> findAllAccounts() {
        return em.createQuery("select a from Account a").getResultList();
    }

    @Override
    public Account getAccount(int accountNumber) {
        return em.find(Account.class, accountNumber);
    }

    @Override
    public void update(Account account) {
        em.merge(account);
    }

}

Note that I haven't gone into placing all the classes and interfaces I used in my demo app. Purpose of the above is for you to see the critical configurations that are needed.

Here's the summary:

  1. A data source is configured in the application server.
  2. A persistence unit is configured (with JTA transaction management).
  3. Spring configuration file is written to process annotations in classes.
  4. The DAO depends on PersistenceContext annotation to access an EntityManager.
  5. Spring is configured to use the JTA transaction manager (of the application server).

You can follow the same style in writing all the DAOs you need. Even though EntityManager instances are not thread safe (but EntityManagerFactory instances are) in general, here you find Spring injecting a an EntityManager instance which is thread safe. In fact, this EntityManager injected is not a real EntityManager instance, but a proxy implementing the same (EntityManager) interface.

When a method (like withdraw) in the AccountManager (business logic) is invoked, we have configured Spring to start a transaction. When a method of DAO is invoked within this transactional context, the proxy EntityManager in DAO receives the method call and goes into searching for a real EntityManager instance associated with the transactional context. If found, that EntityManager instance is used. If not found, the proxy EntityManager will go about creating a new "real" EntityManager instance and associate it with the current transaction. This makes the same "real" EntityManager instance available in multiple DAOs within the same transactional context.

Given that a transaction is associated with a thread, the above configuration also makes sure that the "real" EntityManager instances are correctly used in a thread-safe manner in a multi-threaded environment. That is, a certain "real" EntityManager instance created while processing a request is used only within that thread.

The created "real" EntityManager instances are closed automatically when the transactions are committed or rolled back. Spring automatically does this.

Probkems with Jboss and hibernate

Hello,

I found you article and thought maybe you could help me solve my problem.
the thing is - I created with a Roo spring tool a web app.
I'm trying to deploy it on Jboss 6.0 but I'm having the following error:

Deployment "vfszip:roo_project-0.1.0-SNAPSHOT.war/" is in error due to the following reason(s): java.lang.VerifyError: (class: org/hibernate/bytecode/cglib/CglibClassTransformer, method: doTransform signature: (Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Class;Ljava/security/ProtectionDomain;[B)[B) Incompatible object argument for function call

I feel there is something wrong with the library versions but on the other hand not sure. Maybe you came across such case?

Agata

May be new hibernate libraries...

The client directory of JBoss seems to be having new versions of hibernate jar files. I guess the problem is due to them. You might consider trying after removing them from one place (either your application or JBoss).

Force you hibernate version to be used

Have a look at this post if you're looking for a solution keeping control the used libraries, including Hibernate.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.