JPA (Java Persistence Architecture) greatly simplifies the persistence implementation of Java applications. In web-tier, the following forces/constraints exist:
- Either container managed EntityManagers or application managed EntityManagers should be used.
- Transaction demarcation should either use JTA transaction management (via UserTransaction object) or EntityManager methods.
- A single EntityManager instance is preferred to be used to serve an entire request.
- If you use the DAO (Data Access Object) pattern, typical DAO classes cannot use container managed EntityManagers since their life-cycle is not managed by the Servlet container. Resource injection via annotations is only possible in container managed resources (Servlets, Listeners, or JSF managed beans).
These forces lead to different implementation possibilities. Interestingly, when the combination A is better than B from one perspective, A is worse than B from another perspective. These perspectives include the amount of coding required, amount of object references to be shared, number of objects to be created and discarded while processing a single request, thread safety, ease in managing transactions and making sure EntityManagers are properly closed at the end of request processing.
Which combination is the best then? The best solution that I have found is not indicated in the above list. The best is to use the Spring framework. Another is not to depend on the Spring framework, but depend on JSF.
Spring Framework Approach
Spring framework takes away all the negative issues that you come across when you attempt to use a certain combination of the above. The Spring framework solution is to use EntityManagers in DAO classes that are injected using annotations. You configure DAO classes as Spring managed beans and hence Spring can inject EntityManagers to them via annotations. You will also use Spring transaction support which goes with Spring AOP (Aspect Oriented Programming).
Something notable here (Spring approach) is the Spring provided thread safe EntityManager that you inject into the DAOs so that you can use these DAO instances without discarding them. Neither you really need a DAO factory. You can just initialize the DAOs, inject them into the business code. Let the same DAO instances be reused by business code. No need to use EntityManagerFactory at all. Thread safety is still accomplished. You can use JTA transactions or depend on a resource local transaction manager. I have detailed this solution here.
In combination with the Spring framework, you may use any other web-tier framework like JSF, Struts, Spring MVC and so on. Note that you can go for a Spring + JSF solution.
JSF Only Approach
The other approach I mentioned is not to depend on Spring for persistence, but to use JSF. The strategy is as follows:
- Define DAOs as JSF managed beans in request scope.
- Use @PersistenceContext annotation in DAOs to inject EntityManagers (i.e. use container managed EntityManagers). Container creates and discards the EntityManagers gracefully in coordination with JTA transactions.
- Use JTA transaction demarcation.
- Use a DAO factory that does not cache DAO instances. Instead, go about returning DAO instances by getting them from JSF managed environment each time a DAO is requested (from the DAO factory). Define the DAO factory as a JSF managed been in application scope.
- Pass the DAO factory to the business classes. Let the business classes maintain the reference of the DAO factory.
- Within a business method, start a transaction, get new DAOs from the DAO factory, use them, committee the transaction, forget about the DAO instances.
Let's look at some important code segments.
A typical DAO (uses an injected EntityManager):
public class JSFJPAAccountDAO implements AccountDAO { @PersistenceContext private EntityManager em; public void delete(Account account) { em.remove(account); } public Account find(int pk) { return em.find(Account.class, pk); } public Account save(Account account) { em.persist(account); return account; } public void update(Account account) { em.merge(account); } }
Here's how it is defined as a request scoped managed bean in JSF:
<managed-bean> <managed-bean-name>jSFJPAAccountDAO</managed-bean-name> <managed-bean-class>org.swview.jpatestapp.dao.jsfjpa.JSFJPAAccountDAO</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean>
Here's the DAO factory:
/* * Define this as a managed bean in JSF in the application scope. * Inject to any other managed bean (in any scope) as required. */ public class JSFJPADAOFactory implements DAOFactory { /* * These two properties should be set in the faces configuration file. */ private String accountDAOBeanName; private String customerDAOBeanName; public void setAccountDAOBeanName(String accountDAOBeanName) { this.accountDAOBeanName = accountDAOBeanName; } public void setCustomerDAOBeanName(String customerDAOBeanName) { this.customerDAOBeanName = customerDAOBeanName; } /* * Utility function to locate the JSF managed beans. * * All getXXXDAO() methods should depend on it. * * JSF will return a new bean only if a bean has not been created for the same * request (since all the DAO beans are request scoped). Otherwise, the same * managed bean instance will be returned by JSF. */ private Object getDAO(String name) { FacesContext context = FacesContext.getCurrentInstance(); ELContext elContext = context.getELContext(); ValueExpression ve = context.getApplication() .getExpressionFactory() .createValueExpression( elContext, "#{"+ name + "}", Object.class); return ve.getValue(elContext); } public AccountDAO getAccountDAO() { return ((AccountDAO) getDAO(accountDAOBeanName)); } public CustomerDAO getCustomerDAO() { return ((CustomerDAO) getDAO(customerDAOBeanName)); } }
Here's how it is defined and configured to be an application scoped managed bean in JSF:
<managed-bean> <managed-bean-name>jSFJPADAOFactory</managed-bean-name> <managed-bean-class>org.swview.jpatestapp.dao.jsfjpa.JSFJPADAOFactory</managed-bean-class> <managed-bean-scope>application</managed-bean-scope> <managed-property> <property-name>accountDAOBeanName</property-name> <property-class>java.lang.String</property-class> <value>jSFJPAAccountDAO</value> </managed-property> <managed-property> <property-name>customerDAOBeanName</property-name> <property-class>java.lang.String</property-class> <value>jSFJPACustomerDAO</value> </managed-property> </managed-bean>
Here's the persistence unit configuration (Note that JTA is used. hibernate.hbm2ddl.auto set to "create" will drop tables and recreate them when the application is deployed.):
<?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="BankPU" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:BankDS</jta-data-source> <properties> <!-- Auto-detect entity classes --> <property name="hibernate.archive.autodetection" value="class, hbm"/> <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>
Here's how a typical business method is implemented in a class that captures business logic:
public class BankService { @Resource private UserTransaction utx; private DAOFactory daoFactory; public void setDaoFactory(DAOFactory daoFactory) { this.daoFactory = daoFactory; } public Account createAccount(Customer customer, Account account) throws BankServiceException { try { utx.begin(); // Make sure to access the DAOs within the JTA transaction CustomerDAO customerDAO = daoFactory.getCustomerDAO(); AccountDAO accountDAO = daoFactory.getAccountDAO(); // Create a new customer customerDAO.save(customer); // Create a new Account account.setCustomer(customer); accountDAO.save(account); utx.commit(); } catch (Exception ex) { try { utx.rollback(); } catch (Exception exe) { throw new BankServiceException("Rollback failed: " + exe.getMessage()); } throw new BankServiceException("Commit failed: " + ex.getMessage()); } return account; } }
See the attachment section to download the sample application. It is an Eclipse project deployed in JBoss 5.1.0 and uses MySQL and Hibernate (as the JPA provider).
JBoss data source configuration file is also available for downloading. You will edit it and copy it to the JBoss hot deployment directory (for example, $JBOSS_HOME/server/default/deploy/). You also need the MySQL JDBC driver jar file copied to a suitable folder of JBoss (for example, $JBOSS_HOME/server/default/lib/). If you don't have the MySQL JDBC driver, download it from the MySQL web site.
Summary
Since different possibilities exist for web-tier persistence and transaction management, there is a quest for the best strategy that works well in most of the cases. Even I myself have used strategies that require front filters in intercepting requests and responses to assist in implementing persistence and transaction management in Java applications. The best approach is to use the layered architecture properly where different concerns are fully handled within the appropriate layers.
Spring is surely a winner in assisting in implementing web-tier persistence among many other capabilities it has. JTA or resource local transactions can be used with Spring. Another approach is to depend on container managed EntityManagers with JSF and JTA. In both cases it is relatively easy to make sure that a single EntityManager instance is used to serve an entire HTTP request. This includes requests that may use multiple DAOs. Thread safety is retained. EntityManagers are closed properly. Method calls from one layer to another that bypass intermediate layers are not used and hence the separation of concerns and the proper usage of layers architectural pattern are honored.