Oracle® Application Server 10g Application Developer’s Guide
10g (9.0.4) Part No. B10378-01 |
|
![]() |
![]() |
Recall that the first 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 first sample application consists of listing the employee information, adding benefits, and removing benefits (see Section 3.1, "Requirements for the First Sample Application") for a specific employee.
The database schema for the application, which you might find useful to review, is shown in Section 1.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 first sample application uses EJBs because it might offer more functions to users in the future. The EJB container provides services that the application might need.
The first sample application needs the following EJBs:
An object to manage employee data
The application needs to query the database and display the retrieved data. This can be an entity bean.
An object to contain master benefit data
The application uses this object to determine which benefits a user does not have.
A session bean to manage the employee entity beans
A data access object (DAO)
DAOs are used to connect to the data source. The EJBs do not connect to the data source directly.
A Controller and ActionHandler objects
These objects are needed to implement the MVC design pattern for the application.
Utility objects
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:
You need to model complex business logic.
You need to model complex relationships between business objects.
You need to access your component from different client types such as JSPs and servlets.
You need J2EE services.
Choose servlets when:
You need to maintain state but do not require J2EE services (HttpSession object).
You do not need to dedicate servlet instances to individual clients. In large deployments with thousands of concurrent users, maintaining one stateful session bean instance for each client may be a bottleneck. Servlets provide a lighter weight alternative.
You need to temporarily store state of business process within a single HTTP request and the request involves multiple beans.
Choose normal Java objects when:
You do not need built-in Web and EJB services such as transactions, security, persistence, resource pooling.
You need the following features that are not allowed in EJBs:
accessing a local disk using the java.io
package
creating threads
using the synchronized
keyword
using the java.awt
or javax.swing
packages
listening to a socket or creating a socket server
modifying the socket factory
using native libraries (JNI)
reading or writing static variables
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 */ 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 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.5.6.3, "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 void addBenefits(int benefits[]) throws RemoteException; public void removeBenefits(int benefits[]) throws RemoteException; public EmployeeModel getDetails() 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 the getEmployeeDetails
method in EmployeeManager, which in turn calls the getEmployee
method (see Figure 4-4). The getEmployee
method returns an Employee object, and the EmployeeManager invokes the getDetails
method on this Employee object. The getDetails
method returns an EmployeeModel object to the EmployeeManager, which returns it to the JSP. See Section 6.2, "Query Employee Operation" for 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.
See Chapter 10, " Updating EJBs to Use EJB 2.0 Features" for examples of EJBs that use container-managed persistence.
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 (DAOs) 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 2-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.
http://java.sun.com/blueprints/patterns/DAO.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 can be found in the EmployeeDAOImpl class. 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 session 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 BenefitModel’s. 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 not 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.
Table 4-1 Methods in EmployeeManager for Business Logic Operations
Operation | Method |
---|---|
Query and display employee data | getEmployeeDetails(empID)
|
Add benefits | getUnelectedBenefitItems(empID)
|
Remove benefits | getEmployeeDetails(empID) , which returns EmployeeModel, then getBenefits() on the EmployeeModel
|
Examples:
<% 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 %>