6.16. Iteration 2Move Business Logic Out of the ControllerIn this Iteration, we're going to move all business logic out of the Controller Servlet into the InventoryFacadeBean (which groups our synchronous activities together and wraps the DAO). We'll take the following steps:
6.16.1. Refactoring the Business LogicIf you'll recall from earlier in this chapter, we had modified the Controller Servlet so its viewCarList action used the InventoryFacadeBean to find all available cars. We will now refactor the Controller Servlet by moving all the business logic from its actions into the InventoryFacadeBean. Example 6-21 shows the modifications to the Controller Servlet. Example 6-21. ControllerServlet.java package com.jbossatwork; ... import com.jbossatwork.ejb.*; ... import javax.ejb.*; ... public class ControllerServlet extends HttpServlet { ... protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... InventoryFacadeLocalHome inventoryHome; inventoryHome = (InventoryFacadeLocalHome) ServiceLocator.getEjbLocalHome(InventoryFacadeLocalHome.COMP_NAME); InventoryFacadeLocal inventory = null; try { inventory = inventoryHome.create( ); } catch (CreateException ce) { throw new RuntimeException(ce.getMessage( )); } // perform action if (VIEW_CAR_LIST_ACTION.equals(actionName)) { request.setAttribute("carList", inventory.listAvailableCars( )); destinationPage = "/carList.jsp"; } else if (ADD_CAR_ACTION.equals(actionName)) { request.setAttribute("car", new CarDTO( )); destinationPage = "/carForm.jsp"; } else if (EDIT_CAR_ACTION.equals(actionName)) { int id = Integer.parseInt(request.getParameter("id")); request.setAttribute("car", inventory.findCar(id)); destinationPage = "/carForm.jsp"; } else if (SAVE_CAR_ACTION.equals(actionName)) { // build the car from the request parameters CarDTO car = new CarDTO( ); car.setId(Integer.parseInt(request.getParameter("id"))); car.setMake(request.getParameter("make")); car.setModel(request.getParameter("model")); car.setModelYear(request.getParameter("modelYear")); // save the car inventory.saveCar(car); // prepare the list request.setAttribute("carList", inventory.listAvailableCars( )); destinationPage = "/carList.jsp"; } else if (DELETE_CAR_ACTION.equals(actionName)) { // get list of ids to delete String[ ] ids = request.getParameterValues("id"); // delete the list of ids inventory.deleteCars(ids); // prepare the list request.setAttribute("carList", inventory.listAvailableCars( )); destinationPage = "/carList.jsp"; } ... } } So, rather than making DAO calls directly from the Controller Servlet, we're now calling the InventoryFacadeBean to execute the business logic. Let's show the changes in the InventoryFacadeBean(Example 6-22). Example 6-22. InventoryFacadeBean.javapackage com.jbossatwork.ejb; import java.util.*; import javax.ejb.*; import com.jbossatwork.dao.*; import com.jbossatwork.dto.CarDTO; public class InventoryFacadeBean implements SessionBean { ... /** * @ejb.interface-method * @ejb.transaction * type="Required" * */ public List listAvailableCars( ) throws EJBException { CarDAO carDAO = new HibernateCarDAO( ); return carDAO.filterByStatus(CarDTO.STATUS_AVAILABLE); } /** * @ejb.interface-method * @ejb.transaction * type="Required" * */ public CarDTO findCar(int id) throws EJBException { CarDAO carDAO = new HibernateCarDAO( ); return carDAO.findById(id); } /** * @ejb.interface-method * @ejb.transaction * type="Required" * */ public void deleteCars(String[ ] ids) throws EJBException { CarDAO carDAO = new HibernateCarDAO( ); if (ids != null) { carDAO.delete(ids); } } /** * @ejb.interface-method * @ejb.transaction * type="Required" * */ public void saveCar(CarDTO car) throws EJBException { CarDAO carDAO = new HibernateCarDAO( ); if (car.getId( ) = = -1) { carDAO.create(car); } else { carDAO.update(car); } } } Again, there isn't anything too exciting going on here. We've taken all the DAO-related code from the Controller Servlet and created new methods in the InventoryFacadeBean:
Notice that even the read-only methods (listAvailableCars( ) and findCar( ) ) require a transaction. We need run inside a transaction because of how we're getting the Hibernate Sessionthis is a new wrinkle introduced with Hibernate 3. Each method-level @ejb.transaction tag sets the transaction attribute for an EJB's business method in ejb-jar.xml. We'll cover Hibernate 3 and CMT in the next couple of sections. You may be wondering why we've taken the trouble to factor our business logic out of the Controller Servlet into a Session Bean. We refactored for a couple of reasons:
We could've gone one step further and factored all the business logic in the InventoryFacadeBean into another POJO known as an Application Service, a Core J2EE Pattern. If set up correctly, an Application Service enables you to test your business logic outside of JBoss. But since this isn't a JUnit book, we leave this refactoring to the reader. 6.16.2. Hibernate 3 and CMTUntil now, we've managed Hibernate transactions programmatically. Now that we're inside an EJB and using CMT, we don't have to perform user-managed transactions in Hibernate anymore. Now that the container manages transactions for us, we can remove the Hibernate API calls that set up and tear down our transactions. Example 6-23 contains the original HibernateCarDAO's (first introduced in Chapter 5) read-only findById( ) method that closed its Hibernate Session and the update( ) method that managed its own transaction. Example 6-23. HibernateCarDAO.java... public class HibernateCarDAO implements CarDAO { ... public CarDTO findById(int id) { CarDTO car = null; Session session = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); car = (CarDTO) session.get(CarDTO.class, new Integer(id)); } catch (Exception e) { System.out.println(e); } finally { try { if (session != null) {session.close( );} } catch (Exception e) { System.out.println(e); } } return car; } ... public void update(CarDTO car) { Session session = null; Transaction tx = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); tx = session.beginTransaction( ); session.update(car); tx.commit( ); } catch (Exception e) { try{tx.rollback( );} catch(Exception e2){System.out.println(e2);} System.out.println(e); } finally { try { if (session != null) {session.close( );} } catch (Exception e) { System.out.println(e); } } } ... } Most of the code is concerned with transaction and session setup and tear down. Now let's look at the new HibernateCarDAO that uses Container-Managed Transactions in Example 6-24. Example 6-24. HibernateCarDAO.java... public class HibernateCarDAO implements CarDAO { private List carList; private static final String HIBERNATE_SESSION_FACTORY = "java:comp/env/hibernate/SessionFactory"; public HibernateCarDAO( ){ } public List findAll( ) { List carList = new ArrayList( ); Session session = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); Criteria criteria = session.createCriteria(CarDTO.class); carList = criteria.list( ); } catch (Exception e) { System.out.println(e); } return carList; } public List filterByStatus(String status) { List availableCarList = new ArrayList( ); Session session = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); Criteria criteria = session.createCriteria(CarDTO.class) .add( Restrictions.eq("status", status)); availableCarList = criteria.list( ); } catch (Exception e) { System.out.println(e); } return availableCarList; } public CarDTO findById(int id) { CarDTO car = null; Session session = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); car = (CarDTO) session.get(CarDTO.class, new Integer(id)); } catch (Exception e) { System.out.println(e); } return car; } public void create(CarDTO car) { Session session = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); session.save(car); } catch (Exception e) { System.out.println(e); } } public void update(CarDTO car) { Session session = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); session.update(car); } catch (Exception e) { System.out.println(e); } } public void delete(String[ ] ids) { Session session = null; try { session = ServiceLocator.getHibernateSession( HIBERNATE_SESSION_FACTORY); for (int i = 0; i < ids.length; i++) { CarDTO car = (CarDTO) session.get(CarDTO.class, new Integer(ids[i])); session.delete(car); } } catch (Exception e) { System.out.println(e); } } } You should notice the following differences in the new HibernateCarDAO:
We've retrofitted the HibernateCarDAO so it works with Container-Managed Transactions, but you're not done yet. We also have to modify how we're getting the Hibernate Session in the ServiceLocator's getHibernateSession( ) method. Previously, we called openSession( ), as in Example 6-25. Example 6-25. ServiceLocator.java... public class ServiceLocator { ... public static Session getHibernateSession(String jndiSessionFactoryName) throws ServiceLocatorException { Session session = null; ... session = getHibernateSessionFactory(jndiSessionFactoryName) .openSession( ); ... return session; } ... } To work within a transaction, we had to modify the getHibernateSession( ) method, as in Example 6-26. Example 6-26. ServiceLocator.java... public class ServiceLocator { ... public static Session getHibernateSession(String jndiSessionFactoryName) throws ServiceLocatorException { Session session = null; ... session = getHibernateSessionFactory(jndiSessionFactoryName) .getCurrentSession( ); ... return session; } ... } So, rather than using the Session Factory's openSession( ) method to open a new Session, we call the getCurrentSession( ) method to get the Session that's part of the current transactional context managed by the container. Your code must run within a transaction, or the Session Factory's getCurrentSession( ) method will throw a NullPointerExceptionthat's why each of the InventoryFacadeBean's methods had a transaction setting of Required. |