With Spring 3.x, you can develop JPA applications very easily. Here you find guidelines in creating such an application with the following features:
- Use JTA for transaction management (with very little modifications you can use resource local transactions as well).
- 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.
- 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).
- Spring 3.0.0
- JBoss 5.1.0 (JDK 1.6 version)
- JDK 1.6.0.11
- Hibernate (the one packaged with JBoss 5.1.0)
- Database used: MySQL 5.1.37
- 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:
- A data source is configured in the application server.
- A persistence unit is configured (with JTA transaction management).
- Spring configuration file is written to process annotations in classes.
- The DAO depends on PersistenceContext annotation to access an EntityManager.
- 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.