Oracle9i Application Server Application Developer's Guide Release 2 (9.0.2) Part Number A95101-01 |
|
Recall that the Employee Benefit sample application follows the MVC design pattern. This chapter discusses the model (M) and the controller (C) in the application. The view (V) is covered in Chapter 5, "Creating Presentation Pages".
The business logic for the Employee Benefit application consists of listing the employee information, adding benefits, and removing benefits (see Section 2.1, "Requirements for the Sample Application") for a specific employee.
The database schema for the application, which you might find useful to review, is shown in Section 2.3, "Database Schema".
JSP pages contain presentation data and they also invoke business logic objects to perform certain operations (query employee information, add benefits, and remove benefits). These objects can be plain Java classes or EJB objects.
The Employee Benefit application uses EJBs because it might offer more functions to users in the future. The EJB container provides services that the application might need.
What EJB objects does the application need?
The application needs to query the database and display the retrieved data. This can be an entity bean.
The application uses this object to determine which benefits a user does not have.
DAOs are used to connect to the data source. The EJBs do not connect to the data source directly.
These objects are needed to implement the MVC design pattern for the application.
The application uses utility objects to perform specific tasks. It has a class to print debugging messages, and a class to define constants used by other classes in the application.
The application could have used plain Java classes to hold data and not used EJBs at all. But if the application grows and contains more features, it might be easier to use EJBs because it comes with a container that provides services such as persistence and transactions.
Another advantage of using EJB is that it is easier to find developers who are familiar with the EJB standard. It takes longer for developers to learn a "home-grown" proprietary system.
Here are some guidelines to help you choose among EJBs, servlets, and normal Java objects.
Choose EJBs when:
Choose servlets when:
Choose normal Java objects when:
The Controller servlet is the first object that handles requests for the application. It contains a mapping of actions to classes, and all it does is route requests to the corresponding class.
The init method in the servlet defines the mappings. In this case, the application hardcodes the mappings in the file. It would be more flexible if the mapping information comes from a database or a file.
When the Controller gets a request, it runs the doGet or the doPost method. Both methods call the process method, which looks up the value of the action parameter and calls the corresponding class.
package empbft.mvc; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.HashMap; import empbft.util.*; /** MVC Controller servlet. Implements the controller in an Model View Control pattern. */ public class Controller extends HttpServlet { /* Private static String here, not String creation out of the execution path and hence help to improve performance. */ private static final String CONTENT_TYPE = "text/html"; /** Hashtable of registered ActionHandler object. */ private HashMap m_actionHandlers = new HashMap(); /** ActionHandlerFactory, responsible for instantiating ActionHandlers. */ private ActionHandlerFactory m_ahf = ActionHandlerFactory.getInstance(); /** Servlet Initialization method. @param - ServletConfig @throws - ServletException */ public void init(ServletConfig config) throws ServletException { super.init(config); //Register ActionHandlers in Hashtable, Action name, implementation String //This really ought to come from a configuration file or database etc.... this.m_actionHandlers.put(SessionHelper.ACTION_QUERY_EMPLOYEE, "empbft.mvc.handler.QueryEmployee"); this.m_actionHandlers.put(SessionHelper.ACTION_ADD_BENEFIT_TO_EMPLOYEE, "empbft.mvc.handler.AddBenefitToEmployee"); this.m_actionHandlers.put(SessionHelper.ACTION_REMOVE_BENEFIT_FROM_EMPLOYEE, "empbft.mvc.handler.RemoveBenefitFromEmployee"); } /** doGet. Handle an MVC request. This method expects a parameter "action" http://localhost/MVC/Controller?action=dosomething& aparam=data&anotherparam=moredata @param - HttpServletRequest request, @param - HttpServletResponse response, */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { process(request, response); } /** doPost. Handle an MVC request. This method expects a parameter "action" http://localhost/MVC/Controller?action=dosomething& aparam=data&anotherparam=moredata @param - HttpServletRequest request, @param - HttpServletResponse response, */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { process(request, response); } private void process(HttpServletRequest request, HttpServletResponse response) { try { //Get the action from the request parameter String l_action = request.getParameter(SessionHelper.ACTION_PARAMETER); //Find the implemenation for this action if (l_action == null) l_action = SessionHelper.ACTION_QUERY_EMPLOYEE; String l_actionImpl = (String) this.m_actionHandlers.get(l_action); if (l_actionImpl == null) { throw new Exception("Action not supported."); } ActionHandler l_handler = this.m_ahf.createActionHandler(l_actionImpl); l_handler.performAction(request,response); } catch(Exception e) { e.printStackTrace(); } } }
The process method of the Controller servlet looks up the class that is mapped to the request and calls createActionHandler in the ActionHandlerFactory class to instantiate an instance of the mapped class.
The Employee Benefit application maps three actions to three classes. These classes must be subclasses of the AbstractActionHandler abstract class, which implements the ActionHandler interface, and must implement the performAction method. Figure 4-1 shows the relationship between these classes.
The performAction method checks whether the request came from a browser or a wireless device, and forwards the request to the appropriate JSP file. For browsers the JSP file returns HTML, while for wireless devices the JSP file returns XML. For example, the performAction method in QueryEmployee.java forwards requests from browsers to the queryEmployee.jsp file, but for requests from wireless devices the method forwards the requests to the queryEmployeeWireless.jsp file.
Employee data can be mapped to an Employee entity bean. The home and remote interfaces for the bean declare methods for retrieving employee data and adding and removing benefits.
Each instance of the bean represents data for one employee and the instances can be shared among clients. The EJB container instantiates entity beans and waits for requests to access the beans. By sharing bean instances and instantiating them before they are needed, the EJB container uses instances more efficiently and provides better performance. This is important for applications with a large number of clients.
Entity beans are less useful if the employees table is very large. The reason is that you are using a lot of fine-grained objects in your application.
Internally, the Employee bean stores employee data in a member variable called m_emp of type EmployeeModel. This class has methods for getting individual data items (such as email, job ID, phone).
The Employee entity bean has the following home interface:
package empbft.component.employee.ejb; import java.rmi.RemoteException; import javax.ejb.*; public interface EmployeeHome extends EJBHome { public Employee findByPrimaryKey(int employeeID) throws RemoteException, FinderException; }
The findByPrimaryKey method, which is required for all entity beans, enables clients to find an Employee object. It takes an employee ID as its primary key.
It is implemented in the EmployeeBean class as ejbFindByPrimaryKey. To find an Employee object, it uses a data access object (DAO) to connect to the database and perform a query operation based on the employee ID.
See Section 4-6, "EmployeeDAO classes" for details on DAOs.
The Employee bean's remote interface declares the methods for executing business logic operations:
package empbft.component.employee.ejb; import java.rmi.RemoteException; import javax.ejb.EJBObject; import empbft.component.employee.helper.*; public interface Employee extends EJBObject { public EmployeeModel getDetails() throws RemoteException; public void addBenefits(int benefits[]) throws RemoteException; public void removeBenefits(int benefits[]) throws RemoteException; }
The addBenefits and removeBenefits methods access the database using a DAO and perform the necessary operations. See Section 4.5.6, "Data Access Object for Employee Bean" for details.
The getDetails method returns an instance of EmployeeModel, which contains employee information. The query operation calls this method to get and display employee data. JSP pages call getEmployeeDetails method in EmployeeManagerBean, which call this method (Figure 4-4). Section 6.2, "Query Employee Operation" contains details on the query operation.
The Employee entity bean uses bean-managed persistence (BMP), rather than container-managed persistence. The bean controls when it updates data in the database.
It uses BMP because the employees-to-benefits is a many-to-many relationship, and an old version of Oracle9iAS (release 1022) does not support M:M relationship.
The Employee entity bean implements the ejbLoad method, although the bean uses bean-managed persistence. The ejbLoad method queries the database (using the DAO) and updates the data in the bean with the new data from the database. This ensures that the bean's data is synchronized with the data in the database.
ejbLoad is called after the user adds or removes benefits.
// from EmployeeBean.java public void ejbLoad() { try { if (m_dao == null) m_dao = new EmployeeDAOImpl(); Integer id = (Integer)m_ctx.getPrimaryKey(); this.m_emp = m_dao.load(id.intValue()); } catch (Exception e) { throw new EJBException("\nException in loading employee.\n" + e.getMessage()); } }
See also Section 4.5.6.3, "Load Method", which describes the load method in the DAO.
The implementation of the Employee bean uses a variable of type EmployeeModel, which contains all the employee details such as first name, last name, job ID, and so on. The following code snippet from EmployeeBean shows m_emp as a class variable:
public class EmployeeBean implements EntityBean { private EmployeeModel m_emp; ... }
Code snippet from EmployeeModel:
public class EmployeeModel implements java.io.Serializable { protected int m_id; protected Collection m_benefits; private String m_firstName; private String m_lastName; private String m_email; private String m_phoneNumber; private Date m_hireDate; private String m_jobId; ... }
Data access objects (DAO) are the only classes in the application that communicate with the database, or in general, with a data source. The entity and session beans in the application do not communicate with the data source. See Figure 3-2.
By de-coupling business logic from data access logic, you can change the data source easily and independently. For example, if the database schema or the database vendor changes, you only have to update the DAO.
DAOs have interfaces and implementations. EJBs access DAOs by invoking methods declared in the interface. The implementation contains code specific for a data source.
For details on DAOs, see:
http://java.sun.com/j2ee/blueprints/design_patterns/data_access_object/index.html
The EmployeeDAO interface declares the interface to the data source. Entity and session beans and other objects in the application call these methods to perform operations on the data source.
package empbft.component.employee.dao; import empbft.component.employee.helper.EmployeeModel; public interface EmployeeDAO { public EmployeeModel load(int id) throws Exception; public Integer findByPrimaryKey(int id) throws Exception; public void addBenefits(int empId, int benefits[]) throws Exception; public void removeBenefits(int empId, int benefits[]) throws Exception; }
The implementation of the DAO is called EmployeeDAOImpl. It uses JDBC to connect to the database and execute SQL statements on the database. If the data source changes, you need to update only the implementation, not the interface.
Employee and Benefit objects get an instance of the DAO and invoke the DAO's methods. The following example shows how the addBenefits method in the Employee bean invokes a method in the DAO.
// from EmployeeBean.java public void addBenefits(int benefits[]) { try { if (m_dao == null) m_dao = new EmployeeDAOImpl(); m_dao.addBenefits(m_emp.getId(), benefits); ejbLoad(); } catch (Exception e) { throw new EJBException ("\nData access exception in adding benefits.\n" + e.getMessage()); } }
The addBenefits method in the EmployeeDAOImpl class looks like the following:
public void addBenefits(int empId, int benefits[]) throws Exception { String queryStr = null; PreparedStatement stmt = null; try { getDBConnection(); for (int i = 0; i < benefits.length; i ++) { queryStr = "INSERT INTO EMPLOYEE_BENEFIT_ITEMS " + " (EMPLOYEE_ID, BENEFIT_ID, ELECTION_DATE) " + " VALUES (" + empId + ", " + benefits[i] + ", SYSDATE)"; stmt = dbConnection.prepareStatement(queryStr); int resultCount = stmt.executeUpdate(); if (resultCount != 1) { throw new Exception("Insert result count error:" + resultCount); } } } catch (SQLException se) { throw new Exception( "\nSQL Exception while inserting employee benefits.\n" + se.getMessage()); } finally { closeStatement(stmt); closeConnection(); } }
The methods in EmployeeDAOImpl use JDBC to access the database. Another implementation could use a different mechanism such as SQLJ to access the data source.
After the Employee bean adds or removes benefits for an employee, it calls the load method in EmployeeDAOImpl:
// from EmployeeBean.java public void addBenefits(int benefits[]) { try { if (m_dao == null) m_dao = new EmployeeDAOImpl(); m_dao.addBenefits(m_emp.getId(), benefits); ejbLoad(); } catch (Exception e) { throw new EJBException ("\nData access exception in adding benefits.\n" + e.getMessage()); } } // also from EmployeeBean.java public void ejbLoad() { try { if (m_dao == null) m_dao = new EmployeeDAOImpl(); Integer id = (Integer) m_ctx.getPrimaryKey(); this.m_emp = m_dao.load(id.intValue()); } catch (Exception e) { throw new EJBException("\nException in loading employee.\n" + e.getMessage()); } }
The ejbLoad method in the Employee bean invokes load in the DAO object. By calling the load method after adding or removing benefits, the application ensures that the bean instance contains the same data as the database for the specified employee.
// from EmployeeDAOImpl.java public EmployeeModel load(int id) throws Exception { EmployeeModel details = selectEmployee(id); details.setBenefits(selectBenefitItem(id)); return details; }
Note that the EJB container calls ejbLoad in the Employee bean automatically after it runs the findByPrimaryKey method. See Section 6.2, "Query Employee Operation" for details.
BenefitCatalog is a stateless session bean. It contains master benefit information such as benefit ID, benefit name, and benefit description for each benefit in the benefits table in the database.
The application could have saved the benefit information to entity bean objects, but it uses a session bean instead. The reason for this is that the master benefit information does not change within the application. It is more efficient for a session bean to retrieve the data only once when the EJB container creates the bean.
Because the benefit information does not change, the BenefitCatalog bean does not need a data access object (DAO) to provide database access. The bean itself communicates with the database.
Each instance of the session bean contains all the benefit information. You can create and pool multiple instances for improved concurrency and scalability. If the application used entity beans and you mapped a benefit to a bean, it would have required one instance per benefit.
The bean is stateless so that one bean can be shared among many clients.
The BenefitCatalog session bean has the following home interface:
package empbft.component.benefit.ejb; import java.rmi.RemoteException; import javax.ejb.EJBHome; import javax.ejb.CreateException; public interface BenefitCatalogHome extends EJBHome { public BenefitCatalog create() throws RemoteException, CreateException; }
The create method, which is implemented in BenefitCatalogBean as ejbCreate, queries the benefits table in the database to get a master list of benefits. The returned data (benefit ID, benefit name, benefit description) is saved to a BenefitModel object. Each record (that is, each benefit) is saved to one BenefitModel object.
The application gets a performance gain by retrieving the benefit data when the EJB container creates the bean, instead of when it needs the data. The application can then query the bean when it needs the data.
The BenefitCatalog session bean has the following remote interface:
package empbft.component.benefit.ejb; import java.rmi.RemoteException; import javax.ejb.EJBObject; import java.util.Collection; public interface BenefitCatalog extends EJBObject { public Collection getBenefits() throws RemoteException; public void refresh() throws RemoteException; }
The getBenefits method returns a Collection of BenefitModels. This is the master list of all benefits. This method is called by the EmployeeManager bean (by the getUnelectedBenefitItems method) when the application needs to display a user's unelected benefits. It compares a user's elected benefits against the master list, and displays the benefits that are not elected. The user then selects benefits to add from this list.
The BenefitCatalog bean contains a Collection of BenefitModels. The BenefitModel class contains the details (benefit ID, benefit name, and benefit description) for each benefit.
The BenefitCatalog bean contains a class variable called m_benefits of type Collection. Data in the Collection are of type BenefitModel. Each BenefitModel contains information about a benefit (such as benefit ID, name, and description). BenefitItem is a subclass of BenefitModel.
JSPs call methods in BenefitModel to display benefit information. For example, queryEmployee.jsp calls the getName method to display benefit name.
<% Collection benefits = emp.getBenefits(); if (benefits == null || benefits.size() == 0) { %> <tr><td>None</td></tr> <% } else { Iterator it = benefits.iterator(); while (it.hasNext()) { BenefitItem item = (BenefitItem)it.next(); %> <tr><td><%=item.getName()%></td></tr> <% } // end of while } // end of if %>
EmployeeManager is a stateless session bean that manages access to the Employee entity bean. It is the only bean that JSPs can access directly; JSPs do not directly invoke the other beans (Employee and BenefitCatalog). To invoke methods on these beans, the JSPs go through EmployeeManager.
Generally, a JSP should not get an instance of an entity bean and invoke methods on the bean directly. It needs an intermediate bean that manages session state with clients and implements business logic that deals with multiple beans. Without this intermediate bean, you need to write the business logic on JSPs, and JSPs should have any business logic at all. A JSP's sole responsibility is to present data.
It is stateless because it does not contain data specific to a client.
EmployeeManager contains methods (defined in the remote interface) that JSPs can invoke to execute business logic operations. These methods invoke methods in the Employee and BenefitCatalog beans.
Examples:
In addBenefitToEmployee.jsp:
<% int empId = Integer.parseInt(request.getParameter( SessionHelper.EMP_ID_PARAMETER)); EmployeeManager mgr = SessionHelper.getEmployeeManager(request); Collection unelected = mgr.getUnelectedBenefitItems(empId); ... %>
In removeBenefitFromEmployee.jsp:
<% int empId = Integer.parseInt(request.getParameter( SessionHelper.EMP_ID_PARAMETER)); EmployeeManager mgr = SessionHelper.getEmployeeManager(request); Collection elected = mgr.getEmployeeDetails(empId).getBenefits(); ... %>
The EmployeeManager has the following home interface:
package empbft.component.employee.ejb; import java.rmi.RemoteException; import javax.ejb.*; public interface EmployeeManagerHome extends EJBHome { public EmployeeManager create() throws RemoteException, CreateException; }
The create method does nothing.
The EmployeeManager has the following remote interface:
package empbft.component.employee.ejb; import java.rmi.RemoteException; import javax.ejb.EJBObject; import java.util.Collection; import empbft.component.employee.helper.*; public interface EmployeeManager extends EJBObject { public Employee getEmployee(int id) throws RemoteException; public EmployeeModel getEmployeeDetails(int id) throws RemoteException; public Collection getUnelectedBenefitItems(int id) throws RemoteException; }
getUnelectedBenefitItems in EmployeeManager invokes methods on the BenefitCatalog bean and returns a Collection to the JSP, which iterates through and displays the contents of the Collection.
Methods in EmployeeManager also return non-bean objects to the application. For example, queryEmployee.jsp invokes the getEmployeeDetails method, which returns an EmployeeModel. The JSP can then invoke methods in EmployeeModel to extract the employee data.
// from queryEmployee.jsp <% int id = Integer.parseInt(empId); EmployeeManager mgr = SessionHelper.getEmployeeManager(request); EmployeeModel emp = mgr.getEmployeeDetails(id); ... %> ... <table> <tr><td>Employee ID: </td><td colspan=3><b><%=id%></b></td></tr> <tr><td>First Name: </td><td><b><%=emp.getFirstName()%></b></td> <td>Last Name: </td><td><b><%=emp.getLastName()%></b></td></tr>
Similarly, in removeBenefitFromEmployee.jsp, the page calls getEmployeeDetails to get an EmployeeModel, then it calls the getBenefits method on the EmployeeModel to list the benefits for the employee. The user can then select which benefits should be removed.
// from removeBenefitFromEmployee.jsp <% int empId = Integer.parseInt(request.getParameter( SessionHelper.EMP_ID_PARAMETER)); EmployeeManager mgr = SessionHelper.getEmployeeManager(request); Collection elected = mgr.getEmployeeDetails(empId).getBenefits(); ... %> ... <h4>Select Elected Benefits</h4> <% Iterator i = elected.iterator(); while (i.hasNext()) { BenefitItem b = (BenefitItem) i.next(); %> <input type=checkbox name=benefits value=<%=b.getId()%>><%=b.getName()%><br> <% } // end while %>
The application uses these utility classes:
|
Copyright © 2002 Oracle Corporation. All Rights Reserved. |
|