
| Goals | After completing this chapter, the student will
|
| Prerequisites | In order to complete this chapter successfully, the student must
|
| Objectives | The objective for this chapter is to understand and use the Enterprise Objects Framework for O/R mapping. |
Imagine a database of 100 tables each with 10 columns. Now imagine developing the EJBs for each of those tables. This reveals an apparent problem with BMP EJBs.
BMP EJB development is a lengthy process, even for very simple tables. But there are issues with modifications to the schema of the database, performance, and overall architecural design. There must be a way to avoid the pitfalls that accompany EJBs for persistence.
Enter EOF.
The Enterprise Objects Framework (EOF) turns working with databases into child's play. EOF uses model files that describe information about the schema of the database, custom queries, and any information necessary to make a connection to the database. The EOModeler application is used to create these models; you can create one from scratch, or you can tell it to reverse engineer an existing database. Once the model has been created, the EOModeler application can automatically create the database, if it hasn't been created, and generate the java classes.
The Java classes generated by EOModeler contain accessor and mutator methods for every attribute that has been designated as a class attribute (most likely all of the attributes for a table except for primary keys). Remember all those getXyz() and setXyz() methods we created in our EJBs in the previous chapter? EOModeler handles all that automatically.
Each instantiation of these classes, corresponds to a table in the database (or databases). Coordinating these instances is a class named an EOEditingContext. An EOEditingContext instance represents a sandbox for editing. It ensures that the data in the application is consistent with those stored in the database.
EOModeler looks like this:

The entities shown on the left side of the window represent tables in the database. Selecting an entity displays the attributes that are associated with the entity in the top right area.
| Used to add an entity (table) | |
| Used to add a column (attribute) to the selected entity | |
| Used to add a relationship between entities | |
| Used to create a fetch specification | |
| Used to flatten a property (beyond the scope of this material) | |
| Used to browse the data in the database | |
| Used to create the tables in the database, set up the primary and foreign keys, and generate SQL | |
| Used to generate Java source for the selected entity |
| Used to denote that the selected column is a primary key for the selected entity | |
| Used to denote that the selected column is a class attribute; that is, the column will have accessor and mutator methods in the generated Java source | |
| Used for locking attributes (beyond the scope of this material) |
In EOF, all database work is done in something called an editing context, represented by the EOEditingContext class. An editing context is a local working area that allows you to fetch data from the database, make changes (including adding or deleting entities, or modifying attributes), and then either save those changes back to the database or cancel them. In database terms, this is like doing various SELECT statements, then opening a transaction, doing INSERT, UPDATE, and/or DELETE statements, and doing a COMMIT. In fact, all these things happen under the covers while you're working with the Enterprise Objects Framework; they're simply well-hidden from you.
The entire point of EOF is to abstract away all the details of the actual database. The first step towards this goal is to be able to get data out of the database without using an actual SQL statement, since the SQL could be database-specific. EOF does this with things called fetch specifications.
A fetch specification is a description of a database query. You create fetch specifications within EOModeler, and then you can use them in your Java code to fetch data into your editing context.
Here is an example of a simple fetch specification in our employee model. We will actually create and use this fetch specification later in this chapter.

Once you have a fetch specification defined in your model, you can use this to fetch data from the database. The data ends up in your editing context as Java objects.
First, create an EOEditingContext or (if you have one) get a handle to an existing one. Then retrieve the fetch specification with the static method fetchSpecificationNamed of the EOFetchSpecification class:
EOFetchSpecification.fetchSpecificationNamed( String name, String entityName)
where name is the name of the fetch specification for the entity given by entityName.
Next, create an NSMutableDictionary and add the appropriate key-value pairs that represent the bindings for the qualification. This can be done by invoking the method takeValueForKey method on an instance of NSMutableDictionary.
instanceOfNSMutableDictionary.takeValueForKey( Object someObject, String key)
Then use the static method objectsWithFetchSpecificationAndBindings of the EOUtilities class:
EOUtilities.objectsWithFetchSpecificationAndBindings( EOEditingContext ec, String entityName, EOFetchSpecification fs, NSDictionary bindings)
which will return NSArray of objects, representing the rows in the table entityName that were qualified by the fetch specification and bindings. If there are no bindings required by the fetch specification, null can be specified for the bindings.
To fetch all objects from the database, specify null for the fetch specification and bindings. If no key-value pair for a binding is not present in the dictionary, EOF will ignore the binding.
Here is an example of fetching with and without a fetch specification and bindings:
EOEditingContext ec = new EOEditingContext();
// fetch all objects from the Employee table
NSArray employees = (NSArray)EOUtilities.objectsWithFetchSpecification(
ec, "Employee", null, null);
// display all employees
for (int count = 0; count < employees.count(); count++)
System.out.println((Employee)(employees.objectAtIndex(count)));
// -----------------------------------------
// now fetch just employees in a single department
EOFetchSpecification fs = EOFetchSpecificationNamed("Employee", "fetchByDeptId");
// set the bindings
NSMutableDictionary bindings = new NSMutableDictionary();
bindings.takeValueForKey(new Integer("1"), "deptId");
// fetch the objects
NSArray specialEmployees = (NSArray(EOUtilities.objectsWithFetchSpecification(
ec, "Employee", fs, bindings);
// display just employees in department 1
for (int count = 0; count < specialEmployees.employees(); count++)
System.out.println((Employee)(specialEmployees.objectAtIndex(count)));
To add a row to a table in the database, create an instance of the corresponding class generated by EOModeler. For instance, to create a new employee in the employee table, create an instance of the Employee class, which EOModeler generated as Employee.java. Set the appropriate values with the mutator methods, insert the object into an EOEditingContext, and finally tell the EOEditingContext to save the changes.
Deleting a row is just as easy. To delete an object, tell the EOEditingContext which contains the object to remove it, and then have the EOEditingContext save the changes.
The insertObject method is invoked on an instance of EOEditingContext to add an object to the EOEditingContext.
instanceOfEOEditingContext.insertObject(EOEnterpriseObject someObject)
The deleteObject method is invoked on an instance of EOEditingContext to remove the object.
instanceOfEOEditingContext.deleteObject(EOEnterpriseObject someObject)
Saving changes is done by invoking saveChanges on the instance of EOEditingContext to which the objects have been added. At this point, EOF starts an actual transaction, figures out which SQL statements need to be generated based on the changes you have made within the editing context, issues those SQL statements, and issues a COMMIT.
instanceOfEOEditingContext.saveChanges()
By now, you should be able to guess how to update a row in a database.
Here's some sample code that updates the last name of employee 1 to be Pilgrim:
EOEditingContext ec = new EOEditingContext();
// get the fetch specification for qualifying with an name
EOFetchSpecification fs = EOFetchSpecificationNamed("employee", "fetchByEmployeeId");
// set the bindings for the name
NSMutableDictionary bindings = new NSMutableDictionary();
bindings.takeValueForKey(new Integer("1"), "id");
// fetch the employees
NSArray employees = (NSArray(EOUtilities.objectsWithFetchSpecification(
ec, "Employee", fs, bindings);
// make sure we found the employee we wanted
if (employees.count() == 1) {
// update the "last name" attribute within the editing context
Employee employee = (Employee)(employees.objectAtIndex(0));
employee.setLastName("Pilgrim");
}
// save the changes back to the database
ec.saveChanges();
# add WebObjects frameworks
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaJDBCAdaptor.framework
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaJDBCAdaptor.framework/Resources/Java/javajdbcadaptor.jar
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaEOAccess.framework
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaEOAccess.framework/Resources/Java/javaeoaccess.jar
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaEOControl.framework
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaEOControl.framework/Resources/Java/javaeocontrol.jar
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaFoundation.framework
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaFoundation.framework/Resources/Java/javafoundation.jar
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaXML.framework
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaXML.framework/Resources/Java/javaxml.jar
(Under Windows, you would edit the run.bat startup script, like this:)
REM add WebObjects frameworks
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaJDBCAdaptor.framework
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaJDBCAdaptor.framework/Resources/Java/javajdbcadaptor.jar
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaEOAccess.framework
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaEOAccess.framework/Resources/Java/javaeoaccess.jar
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaEOControl.framework
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaEOControl.framework/Resources/Java/javaeocontrol.jar
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaFoundation.framework
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaFoundation.framework/Resources/Java/javafoundation.jar
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaXML.framework
set JBOSS_CLASSPATH=%JBOSS_CLASSPATH%;../lib/JavaXML.framework/Resources/Java/javaxml.jar
JBOSS_CLASSPATH=$JBOSS_CLASSPATH:../lib/JavaJDBCAdaptor.framework/Resources/Java/EmployeeEO.jar
That's it! Configuration of JBoss is complete. Now let's see what it takes to convert our case study to use the Enterprise Objects Framework.
Since the case study was designed with a clear separation of presentation and business logic, very few of the JSP files need any changes. The EJBs have been replaced by the EOEnterpriseObjects classes (generated by EOModeler). That leaves only the Struts Action classes, each of which will have to be changed to invoke the EOEnterpriseObjects instead of the EJBs.
First, we will create a model from our existing employee database that we created in the previous chapter. EOModeler can reverse-engineer an existing database to create a model from it automatically.
(If you have not already done so, you can download this and other examples used in this course. Mac OS X or other UNIX users click here instead.)
Do this:
Username: admin
Password: admin
URL: jdbc:openbase://127.0.0.1/employee
(If you get an error saying that the database is not started, open OpenBase Manager, select the employee database, and click Start.)
You should now have an untitled model. In order for WebObjects to find it at runtime, it needs to be in a specific folder, so let's just save it there now. The correct location is deep within your JBoss installation, in lib/JavaJDBCAdaptor.framework/Resources/. Call it employee.eomodel.
Now let's look at the model we've created. It has 3 entities, City, Department, and Employee. These should look familiar; they're the same as the tables in the underlying database. Clicking on City reveals 3 attributes, cityId, name, and rowid. These correspond to database columns. (You can ignore the rowid; that's just an internal OpenBase pseudo-column.)
Similarly, the Department entity has attributes like departmentId, name, phone, and secretaryId. Employee has cityId, departmentId, employeeId, extension, firstname, and lastname.
Each entity should have one attribute with a key icon next to it; this is the primary key.
Note that some of the attribute names do not exactly match the underlying database columns. For instance, the employee table has a primary key of employee_id, but it shows up in EOModeler as employeeId. This is intentional; in your Java code, you will refer to the employeeId attribute of the Employee class, and WebObjects will map that to the original employee.employee_id database column.
Now let's create the fetch specifications we need. The first one we need is fetchById, which will fetch an employee by the primary key, employeeId.
Do this:
We also need additional fetch specifications for fetching all rows from each table.
Do this:
EOModeler can create .java files that are wrappers for accessing each entity in the model. These are analagous to EJBs in a J2EE application, but they inherit from the WebObjects framework classes instead of Sun's EJB framework classes. Under the covers, EOModeler uses its own template language and a template file to generate these. This is stored in c:\Apple\Library\PrivateFrameworks\EOModelWizard.framework\Resources\EOJavaClass.template.
Unfortunately, this file needs a minor revision for use with Struts, which requires all accessor and mutator methods to be named getXyz() and setXyz().
Do this:
Immediately after that is a line that looks like this:
public $property.javaValueClassName$ $property.name$() {
Change this to
public $property.javaValueClassName$ get$property.name$() {
OK, we're finally ready to generate the .java files from our model.
Do this:
And now we can create the rest of the files. As we mentioned earlier, most of the JSPs and all of the Struts configuration files can be copied directly from the previous chapter's code example. There's one JSP which needs to be changed, plus all the Action subclasses for Struts. The EJBs are gone; they have been completely replaced by the .java files that we just generated from EOModeler.
Do this:
| code/Chapter8/Employee |
|---|
Employee
|
+-- employeeadd.jsp (*)
|
+-- employeeaddfailure.jsp (*)
|
+-- employeeaddsuccess.jsp (*)
|
+-- employeedeletefailure.jsp (*)
|
+-- employeedeletesuccess.jsp (*)
|
+-- employeeviewfailure.jsp (*)
|
+-- employeemodify.jsp (*)
|
+-- index.jsp (*)
|
+-- employeeviewsuccess.jsp (*)
|
+-- build.xml (*)
|
+-- employeemodifyfailure.jsp (*)
|
+-- WEB-INF
| |
| +-- ApplicationResources.properties (*)
| |
| +-- web.xml (*)
| |
| +-- app.tld (*)
| |
| +-- struts-bean.tld (*)
| |
| +-- struts-form.tld (*)
| |
| +-- struts-html.tld (*)
| |
| +-- struts-logic.tld (*)
| |
| +-- struts-template.tld (*)
| |
| +-- struts.tld (*)
| |
| +-- struts-config.xml (*)
| |
| +-- classes
| | |
| | +-- Chapter8Utils.java (*)
| | |
| | +-- com
| | |
| | +-- masslight
| | |
| | +-- Employee
| | |
| | +-- EmployeeAddForm.java (*)
| | |
| | +-- EmployeeAddAction.java (*)
| | |
| | +-- EmployeeModifyAction.java (*)
| | |
| | +-- EmployeeViewAction.java (*)
| | |
| | +-- EmployeeModifySetupAction.java (*)
| | |
| | +-- EmployeeModifyForm.java (*)
| | |
| | +-- EmployeeAddSetupAction.java (*)
| | |
| | +-- EmployeeDeleteAction.java (*)
| | |
| | +-- EmployeeModifySetupForm.java (*)
| | |
| | +-- EmployeeDeleteForm.java (*)
| |
| +-- lib
| |
| +-- struts.jar (*)
|
+-- EO
|
+-- City.java (*)
|
+-- Department.java (*)
|
+-- Employee.java (*)
(*) denotes a file |
| code/Chapter8/Employee/employeeviewsuccess.jsp |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/Chapter8Utils.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeAddForm.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeAddSetupAction.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeDeleteAction.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeDeleteForm.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeModifyAction.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeModifyForm.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeModifySetupAction.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeModifySetupForm.java |
|---|
|
| code/Chapter8/Employee/WEB-INF/classes/com/masslight/Employee/EmployeeViewAction.java |
|---|
|
| code/Chapter8/Employee/build.xml |
|---|
|
Discuss the benefits of using an O/R mapping framework. Compare the development times of the EJB and EOF versions of the applications. Also, compare the complexity of each application. What implications are caused by database schema changes?