Oracle® Application Server 10g Application Developer’s Guide
10g (9.0.4) Part No. B10378-01 |
|
![]() |
![]() |
This chapter describes the implementation details of the second sample application. The first part of the chapter describes the business methods in the application. The second part describes how the EJBs in the application map to database tables.
Contents of this chapter:
The second sample application provides business operations that clients can invoke through Web Services. The third sample application (described in Chapter 11, " Enabling Web Services in the Application") is an example of such a client.
The business operations involve accessing and updating data in the HR schema, which is the same schema used in the first sample application.
The application uses EJBs to implement the business operations. The EJBs use features such as container-managed persistence, EJB query language, local interfaces, and container-managed relationships.
Note that the second sample application does not contain any JSPs or servlets because it does not display any pages. The application simply contains EJBs and some supporting Java classes. Its operations are accessed by clients through Web Services, and it is the client applications that display the results.
The second sample application implements these business operations:
List all benefits
Add new benefits
Count the number of employees
List the benefits an employee has
Count the number of employees enrolled in a specified benefit
To implement the operations, the application uses entity beans and a session bean:
Table 10-1 EJBs in the Second Sample Application
Later sections in this chapter describe each business operation in detail.
Entity beans provide a flexible model: they can create new tables and columns and populate them, or they can work with existing tables. In the sample application, they work with existing tables.
Figure 10-1 shows how the EJBs in the application work together. It shows:
business methods in the EmployeeBenefitManager session bean
some of the fields and methods in the Employee and Benefit entity beans
an employee can be associated with zero or more benefits, and a benefit can be associated with zero or more employees
The second sample application follows the "session facade" design pattern. This means that the methods in the entity beans are hidden from clients. Instead, clients call methods in the EmployeeBenefitManager session bean to invoke business operations. Methods in the session bean invoke other methods in the entity beans. Clients do not even know about the existence of the entity beans.
The entity beans use features from the EJB 2.0 specification. In particular, they use these features:
container-managed persistence (CMP) instead of bean-managed persistence (BMP)
container-managed relationships between employees and their benefit items
local interfaces instead of remote interfaces
EJB Query Language (QL)
Note that these features do not apply to the EmployeeBenefitManager session bean. This session bean uses a remote interface (not local interface) so that remote clients can invoke it. Remote clients are clients that run in a different JVM or in a different application. If the session bean used a local interface, then only clients running in the same application can invoke it.
The entity beans contain fields for data that are stored in database tables. Examples of fields in the Employee entity bean are Employee ID, First Name, Last Name, and Hire Date. Examples of fields in the Benefit entity bean are Benefit ID, Benefit Name, and Description.
To enable the EmployeeBenefitManager session bean to access the fields in the entity beans, you do these tasks for each field:
Declare accessor methods (get and set methods) for each field in the local interface (EmployeeLocal.java
and BenefitLocal.java
).
For example, for the First Name field, you would have the getFirstName
and setFirstName
methods. See Section 10.7.2, "Persistent Fields in the Local Interface" for details.
Create abstract methods for the accessor methods in the bean implementation class (EmployeeBean.java
and BenefitBean.java
).
The container takes the abstract methods and implements the code to perform the get or set operation. The container connects to the database to get or set the values.
Map the fields to table columns in the orion-ejb-jar.xml
file. This is required because the field names do not match the names of the table columns in the database. See Section 10.7.3, "Persistent Fields in the orion-ejb-jar.xml File" for details.
To get the number of employees, a client calls the employeeCount
method in the EmployeeBenefitManager session bean. This method calls the corresponding method in the Employee entity bean. The client does not invoke the method in the Employee entity bean directly.
// EmployeeBenefitManagerBean.java public int employeeCount() { try { return getEmployeeLocalHome().employeeCount(); } catch(Exception e) { e.printStackTrace(); throw new EJBException(e); } } // The following method gets the local home object for the Employee bean. private EmployeeLocalHome getEmployeeLocalHome() throws NamingException { final InitialContext context = new InitialContext(); return (EmployeeLocalHome)context.lookup("java:comp/env/ejb/EmployeeLocal"); }
In the EmployeeLocalHome interface, the employeeCount
method executes the ejbHomeEmployeeCount
method in the EmployeeBean class:
// EmployeeBean.java public int ejbHomeEmployeeCount() { try { return ejbSelectAllEmployees().size(); } catch(FinderException fe) { return 0; } } public abstract Collection ejbSelectAllEmployees() throws FinderException;
The ejbSelectAllEmployees
method uses EJB QL to get its results. The name of the method and its EJB QL statement are defined in the ejb-jar.xml
file:
// ejb-jar.xml <entity> <description>Entity Bean ( CMP )</description> <display-name>Employee</display-name> <ejb-name>Employee</ejb-name> ... lines omitted ... <abstract-schema-name>Employee</abstract-schema-name> ... lines omitted ... <query> <query-method> <method-name>ejbSelectAllEmployees</method-name> <method-params/> </query-method> <ejb-ql>select object(e) from Employee e</ejb-ql> </query> ... lines omitted ... </entity>
The <method-name>
element specifies the name of the method, and the <ejb-ql>
element specifies the EJB QL statement to execute when the method is invoked. The <abstract-schema-name>
element specifies the name to use in EJB QL statements to identify entity beans.
In the EJB QL statement, the Employee
reference matches the name specified in the <abstract-schema-name>
element. The statement selects all Employee entity beans and returns a Collection
of Employee beans to the ejbHomeEmployeeCount
method. The method uses the size
method to determine the number of elements in the Collection
.
To get a list of all benefits, a client calls the listBenefits
method in the EmployeeBenefitManager session bean. This method calls the findAll
method in the Benefit bean.
// EmployeeBenefitManagerBean.java public BenefitModel[] listBenefits() { int count = 0; BenefitModel[] returnBenefits; Collection allBenefits = null; BenefitLocal benefitLocal = null; BenefitModel benefit = null; try { allBenefits = getBenefitLocalHome().findAll(); returnBenefits = new BenefitModel[allBenefits.size()]; Iterator iter = allBenefits.iterator(); while(iter.hasNext()) { benefitLocal = (BenefitLocal)iter.next(); benefit = new BenefitModel(benefitLocal.getBenefitId(), benefitLocal.getName(), benefitLocal.getDescription()); returnBenefits[count++]=benefit; } return returnBenefits; } catch(Exception e) { e.printStackTrace(); throw new EJBException(e); } }
The findAll
method is a special method. You declare the method in the BenefitLocalHome interface, but do not implement it in the BenefitBean class. The container implements it for you. You do not have to define a query statement for the method in the ejb-jar.xml
file (it gets generated automatically for you).
Example 10-1 BenefitLocalHome.java
// BenefitLocalHome.java public interface BenefitLocalHome extends EJBLocalHome{ BenefitLocal create() throws CreateException; BenefitLocal findByPrimaryKey(Long primaryKey) throws FinderException; Collection findAll() throws FinderException; BenefitLocal create(Long benefitId, String name) throws CreateException; BenefitLocal findByName(String name) throws FinderException;}
The findAll
method returns a Collection
of BenefitLocal instances. The listBenefits
method iterates through the Collection
and saves the members into an array. It then returns an array of BenefitModel
instances to the client.
The BenefitModel class is a JavaBean that simply contains the fields for a Benefit object.
// BenefitModel.java package empbft.component.model; import java.io.Serializable; public class BenefitModel implements Serializable { private Long _id; private String _name; private String _description; /** * No-Arg constructor required to satisfy contract as a JavaBean. Do * <B>NOT</B> use this constructor. It will throw a RuntimeException * when used. * * @throws RuntimeException */ public BenefitModel() { throw new RuntimeException(getClass().getName() + ": This is not a valid constructor for this object."); } /** * Constructs a new benefit model containing the details for the benefit. */ public BenefitModel(Long id, String name, String description) { this._id = id; this._name = name; this._description = description; } public String getDescription() { return _description; } public void setDescription(String new_description) { _description = new_description; } public Long getId() { return _id; } public void setId(Long new_id) { _id = new_id; } public String getName() { return _name; } public void setName(String new_name) { _name = new_name; } }
To add a new benefit, a client calls the addNewBenefit
method in the EmployeeBenefitManager session bean. This method calls the create
method in the BenefitLocalHome interface.
// EmployeeBenefitManagerBean.java public void addNewBenefit(Long benefitId, String benefitName, String benefitDescription) { try { BenefitLocal newBenefit = getBenefitLocalHome().create(benefitId,benefitName); if(benefitDescription!=null) newBenefit.setDescription(benefitDescription); } catch(Exception e) { e.printStackTrace(); throw new EJBException("Error adding new benefit item : " + e); } }
The create
method in the BenefitLocalHome interface executes the ejbCreate
method in the BenefitBean class. In the ejbCreate
method, you populate the benefit ID and name fields, and return null. The container does the actual work of creating the bean and returning the bean to the caller. The return value type for the ejbCreate
method is the same as the primary key type for the entity bean.
// BenefitBean.java public Long ejbCreate(Long benefitId, String name) { setBenefitId(benefitId); setName(name); return null; }
The addNewBenefit
method then adds a description to the new benefit if the client provided a description.
To get a list of benefits for a specified employee, a client calls the listBenefitsOfEmployee
method in the EmployeeBenefitManager session bean. This method calls the getBenefits
method in the Employee bean, which returns a Collection
of Benefit local interface objects for the specified employee.
// EmployeeBenefitManagerBean.java public BenefitModel[] listBenefitsOfEmployee(long employeeId) { EmployeeLocal employee = null; Collection allBenefits = null; BenefitModel benefits[]; BenefitLocal benefit = null; BenefitModel benefitModel = null; try { // Find the employee, then get their benefits employee = getEmployeeLocalHome().findByPrimaryKey(new Long(employeeId)); allBenefits = employee.getBenefits(); benefits = new BenefitModel[allBenefits.size()]; Iterator iter = allBenefits.iterator(); int count = 0; while(iter.hasNext()) { benefit = (BenefitLocal)iter.next(); benefitModel = new BenefitModel( (Long)benefit.getPrimaryKey(), benefit.getName(), benefit.getDescription()); benefits[count++] = benefitModel; } return benefits; } catch(NamingException ne) { ne.printStackTrace(); throw new EJBException("Could not find Employee " + employeeId); } catch(FinderException fe) { fe.printStackTrace(); throw new EJBException("Could not find Employee " + employeeId); } }
The listBenefitsOfEmployee
method takes an employee ID as an input parameter. It calls the findByPrimaryKey
method in the Employee bean to get the desired Employee instance. It then calls the getBenefits
method on that instance.
Like other get and set methods, the getBenefits
method is an abstract method implemented by the EJB container.
The getBenefits
method returns a Collection
to the listBenefitsOfEmployee
method (in EmployeeBenefitManager), which then extracts the contents of the Collection
into an array of BenefitModel
’s to return to the client.
Note that the listBenefitsOfEmployee
and countEnrollmentsForBenefit
methods (described in the next section) use both the Employee and Benefit entity beans. This requires a relationship field. See Section 10.8, "Relationship Fields in the Entity Beans".
To get the number of employees enrolled in a specified benefit, a client calls the countEnrollmentsForBenefit
method in the EmployeeBenefitManager session bean. This method calls the getEmployees
method in the Benefit bean.
// EmployeeBenefitManagerBean.java public int countEnrollmentsForBenefit(long benefitId) { try { BenefitLocal benefit = getBenefitLocalHome().findByPrimaryKey(new Long(benefitId)); Collection employees = benefit.getEmployees(); return employees.size(); } catch(Exception e) { e.printStackTrace(); throw new EJBException(e); } }
The EmployeeBenefitManager session bean uses the findByPrimaryKey
method to locate the Benefit bean instance that represents the benefit in question. The findByPrimaryKey
method is implemented for you by the container. You declare it in the BenefitLocalHome interface (shown in Example 10-1), but do not define it in the BenefitBean class.
The countEnrollmentsForBenefit
method calls the getEmployees
method on the instance returned by findByPrimaryKey
to get a list of employees, and then calls the size
method to get the number of employees in the list.
Note that the countEnrollmentsForBenefit
method and listBenefitsOfEmployee
method (described in the previous section) use both the Employee and Benefit entity beans. This requires a relationship field. See Section 10.8, "Relationship Fields in the Entity Beans".
The Employee and Benefit entity beans use container-managed persistence (CMP), which means you have to map the persistent fields in the entity beans to table columns. (You can set the container to do automatic mapping; see the Oracle Application Server Containers for J2EE Enterprise JavaBeans Developer's Guide for details.)
Most of the fields in the Employee and the Benefit entity beans map to columns in the EMPLOYEES and BENEFIT tables in the database. Because the beans have different names from the tables, and the names of the fields in the beans do not match the column names, you have to map the names manually in the ejb-jar.xml
and orion-ejb-jar.xml
files.
In the ejb-jar.xml
file, the <cmp-field><field-name>
elements define the persistent fields. For example:
// ejb-jar.xml <entity> <description>Entity Bean ( CMP )</description> <display-name>Employee</display-name> <ejb-name>Employee</ejb-name> <local-home>empbft.component.employee.ejb20.EmployeeLocalHome</local-home> <local>empbft.component.employee.ejb20.EmployeeLocal</local> <ejb-class>empbft.component.employee.ejb20.EmployeeBean</ejb-class> <persistence-type>Container</persistence-type> ... lines omitted ... <cmp-field><field-name>employeeId</field-name></cmp-field> <cmp-field><field-name>firstName</field-name></cmp-field> <cmp-field><field-name>lastName</field-name></cmp-field> <cmp-field><field-name>emailAddress</field-name></cmp-field> <cmp-field><field-name>phoneNumber</field-name></cmp-field> <cmp-field><field-name>hireDate</field-name></cmp-field> ... lines omitted ... </entity> <entity> <description>Entity Bean ( CMP )</description> <display-name>Benefit</display-name> <ejb-name>Benefit</ejb-name> <local-home>empbft.component.benefit.ejb20.BenefitLocalHome</local-home> <local>empbft.component.benefit.ejb20.BenefitLocal</local> <ejb-class>empbft.component.benefit.ejb20.BenefitBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Long</prim-key-class> ... lines omitted ... <cmp-field><field-name>benefitId</field-name></cmp-field> <cmp-field><field-name>name</field-name></cmp-field> <cmp-field><field-name>description</field-name></cmp-field> ... lines omitted ... </entity>
In the local interfaces, EmployeeLocal and BenefitLocal, for each persistent field, you declare a pair of accessor methods (get and set methods).
// EmployeeLocal.java package empbft.component.employee.ejb20; import javax.ejb.EJBLocalObject; import java.util.Collection; import java.sql.Timestamp; public interface EmployeeLocal extends EJBLocalObject { Long getEmployeeId(); void setEmployeeId(Long newEmployeeId); String getFirstName(); void setFirstName(String newFirstName); String getLastName(); void setLastName(String newLastName); ... lines omitted ... Timestamp getHireDate(); void setHireDate(Timestamp newHireDate); ... lines omitted ... Collection getBenefits(); void setBenefits(Collection newBenefits);}
// BenefitLocal.java package empbft.component.benefit.ejb20; import javax.ejb.EJBLocalObject; import java.util.Collection; public interface BenefitLocal extends EJBLocalObject { Long getBenefitId(); void setBenefitId(Long newBenefitId); String getName(); void setName(String newName); String getDescription(); void setDescription(String newDescription); Collection getEmployees(); void setEmployees(Collection newEmployees);}
You map the persistent fields in the entity beans to table columns using the orion-ejb-jar.xml
file. The following lines from the orion-ejb-jar.xml
file show the mappings of some fields in the Employee entity bean.
Tip: If you do not want to create theorion-ejb-jar.xml file from scratch, let OC4J generate a version of the file. You can then edit the generated file and enter the correct values. To do this:
|
// orion-ejb-jar.xml <entity-deployment name="Employee" copy-by-value="false" data-source="jdbc/OracleDS" exclusive-write-access="false" location="Employee" table="EMPLOYEES"> <primkey-mapping> <cmp-field-mapping name="employeeId" persistence-name="EMPLOYEE_ID" persistence-type="number(6)"/> </primkey-mapping> <cmp-field-mapping name="firstName" persistence-name="FIRST_NAME" persistence-type="VARCHAR2(20)"/> <cmp-field-mapping name="lastName" persistence-name="LAST_NAME" persistence-type="VARCHAR2(25)"/> <cmp-field-mapping name="hireDate" persistence-name="HIRE_DATE" persistence-type="DATE"/> ... lines omitted ... </entity-deployment>
Table 10-2 describes some attributes in the <entity-deployment>
element.
Table 10-2 Description of Some Attributes in the <entity-deployment> Element
Attribute | Description |
---|---|
name
|
Identifies the entity bean. This value matches the name specified in the <ejb-name> element in the ejb-jar.xml file.
|
table
|
Identifies the table in the database. |
location
|
Specifies the JNDI name of the entity bean. |
data-source
|
Identifies the database. This value refers to the database pointed to in the data-sources.xml file. |
The <cmp-field-mapping>
element maps fields to columns. The name
attribute specifies the field name, and persistence-name
specifies the column name.
Most of the persistent fields in the Employee and Benefit entity beans map cleanly to corresponding columns in the EMPLOYEES and BENEFITS tables in the database. Also, the countEmployees
method accesses the Employee bean only, and the listBenefits
and the addNewBenefit
methods access the Benefit bean only.
However, the listBenefitsOfEmployee
and the countEnrollmentsForBenefit
methods use both beans each.
The listBenefitsOfEmployee
method takes an employee ID and returns the benefits selected by the employee. An employee can have zero or more benefits.
The countEnrollmentsForBenefit
method takes a benefit ID and returns the number of employees who are signed up for the benefit. A benefit can have zero or more enrollees.
For these two methods to work, you need to set up a many-to-many relationship between the Employee and Benefit entity beans. The relationship type is many-to-many so that you can look up benefits if you know an employee ID, and you can look up employees if you know a benefit ID.
To determine which employees have which benefits, the many-to-many relationship needs to access the EMPLOYEE_BENEFIT_ITEMS table in the database. This is an association table that enables an employee to have more than one benefit, and a benefit to be associated with more than one employee.
Table 10-3 shows some sample rows in the EMPLOYEE_BENEFIT_ITEMS table. The sample rows show that employee ID 101 has two benefits, and benefit ID 1 has two enrollees.
Table 10-3 Sample Data in EMPLOYEE_BENEFIT_ITEMS Table
EMPLOYEE_ID | BENEFIT_ID | ELECTION_DATE |
---|---|---|
101 | 1 | 1/5/2003 |
101 | 2 | 1/5/2003 |
102 | 1 | 1/6/2003 |
You set up relationships in the ejb-jar.xml
file using the <relationships>
element. Under this parent element, the <ejb-relation>
element defines each relationship.
Relationships work with fields. The names of the relationship fields are defined in the <cmr-field-name>
element. In this case, the names of the relationship fields are "benefits" and "employees".
// ejb-jar.xml <relationships> <ejb-relation> <ejb-relation-name>Employee-Has-Benefits</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name>Employee-Has-Benefits </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>Employee</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>benefits</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name>Benefits-Are-For-Employees </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>Benefit</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>employees</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships>
Relationship fields have these features in common with persistent fields:
You must define get and set methods for the fields in the local interface. The names of the methods are getBenefits
and setBenefits
for the Employee entity bean, and getEmployees
and setEmployees
for the Benefit entity bean.
You map the relationship fields to table columns in the orion-ejb-jar.xml
file. Example 10-2 shows the mapping for the Employee bean; Example 10-3 shows the mapping for the Benefit bean.
Example 10-2 <entity-deployment> Section for Employee Entity Bean
// orion-ejb-jar.xml <entity-deployment name="Employee" copy-by-value="false" data-source="jdbc/OracleDS" exclusive-write-access="false" location="Employee" table="EMPLOYEES"> ... other cmp-field-mapping elements omitted ... <cmp-field-mapping name="benefits"> <collection-mapping table="EMPLOYEE_BENEFIT_ITEMS"> <primkey-mapping> <cmp-field-mapping name="employeeId"> <entity-ref> <cmp-field-mapping name="employeeId" persistence-name="EMPLOYEE_ID" persistence-type="NUMBER(6)"/> </entity-ref> </cmp-field-mapping> </primkey-mapping> <value-mapping type="empbft.component.benefit.ejb20.BenefitLocal"> <cmp-field-mapping name="benefitId"> <entity-ref> <cmp-field-mapping name="benefitId" persistence-name="BENEFIT_ID" persistence-type="NUMBER(6)"/> </entity-ref> </cmp-field-mapping> </value-mapping> </collection-mapping> </cmp-field-mapping> </entity-deployment>
Note the following:
The name
attribute for the <cmp-field-mapping>
element is the same as the relationship name defined in the ejb-jar.xml
file.
The table
attribute for the <collection-mapping>
element specifies the name of the association table.
The persistence-name attribute for the <cmp-field-mapping>
element specifies the name of the primary key column.
Under the <primkey-mapping>
element, the <cmp-field-mapping>
element specifies the name of the foreign key column in the association table that maps to the primary key for the current bean (that is, the Employee bean).
Under the <value-mapping>
element, the <cmp-field-mapping>
element specifies the name of the foreign key column in the association table that maps to the primary key for the target bean (that is, the Benefit bean).
Example 10-3 shows the contents of the <entity-deployment>
element for the Benefit entity bean. Its contents are similar to that of the Employee bean.
Note that the values in the <primkey-mapping>
and the <value-mapping>
elements are reversed from the Employee bean’s. The <primkey-mapping>
element for the Benefit bean specifies the BENEFIT_ID column, and the <value-mapping>
element specifies the EMPLOYEE_ID column.
Example 10-3 <entity-deployment> Section for the Benefit Entity Bean
// orion-ejb-jar.xml <entity-deployment name="Benefit" copy-by-value="false" data-source="jdbc/OracleDS" exclusive-write-access="false" location="Benefit" table="BENEFITS"> <primkey-mapping> <cmp-field-mapping name="benefitId" persistence-name="BENEFIT_ID" persistence-type="number(6)"/> </primkey-mapping> <cmp-field-mapping name="name" persistence-name="BENEFIT_NAME" persistence-type="varchar2(50)"/> <cmp-field-mapping name="description" persistence-name="BENEFIT_DESCRIPTION" persistence-type="varchar2(255)"/> <cmp-field-mapping name="employees"> <collection-mapping table="EMPLOYEE_BENEFIT_ITEMS"> <primkey-mapping> <cmp-field-mapping name="benefitId"> <entity-ref home="Benefit"> <cmp-field-mapping name="benefitId" persistence-name="BENEFIT_ID" /> </entity-ref> </cmp-field-mapping> </primkey-mapping> <value-mapping type="empbft.component.employee.ejb20.EmployeeLocal"> <cmp-field-mapping name="employeeId"> <entity-ref home="Employee"> <cmp-field-mapping name="employeeId" persistence-name="EMPLOYEE_ID" /> </entity-ref> </cmp-field-mapping> </value-mapping> </collection-mapping> </cmp-field-mapping> </entity-deployment>