The lookup methods to find existing business object instances are a bit simpler than the creation methods. In the case of regular Java objects, they can simply use utility meth-ods on the JDBCUtilityobject to query the database and instantiate the populated business object. For the Castor objects, however, these methods can encapsulate basic object queries used to locate existing instances. A query in OQL is very similar to a SQL query. For example, the query to locate an Accountobject by its primary key is as follows:
SELECT a FROM Account a WHERE id = $1
The dollar signs represent placeholders for run-time values to be bound to the query.
Remember that the WHERE clause in OQL queries refers to actual property names rather than database columns. OQL queries automatically take care of database joins when the WHERE clause refers to associated objects. As an example, the Account object has a ‘customer’ property that links it back to the owner of the account.
The query to obtain the collection of accounts for a given customer would then be as follows:
SELECT a FROM Account a WHERE customer = $1
This query would take the primary key property of the customer as an argument.
Find by Primary Key
From these examples, you can see that these queries can easily be generated if you have the information in the business object metadata. Thus, you have the findBy-PrimaryKeymethod on the CastorFactoryImplclass. This is a static method, so you pass in the Databaseinstance being used by the transaction in order to create the query object. This is needed on all of the persistence methods that use Castor.
N OT E
/*
* Discover an instance of a business object with the
* given key object.
*/
public static BusinessObject findByPrimaryKey(
String objectName, Object keyObject, Database db) throws BlfException {
// Obtain the business object metadata.
BusinessObjectMetadata bom =
MetadataManager.getBusinessObject(objectName);
try {
// Create the arguments for the // OQL string.
Object [] args = new Object[2];
args[0] = bom.getBusObjClass();
args[1] = bom.getKeyField().getName();
// Create a standard OQL string // to look up by primary key.
String oqlString = MessageFormat.format(
"SELECT b FROM {0} b WHERE {1} = $1", args);
// Create the query and bind the // arguments.
OQLQuery busobjOql = db.getOQLQuery( oqlString );
busobjOql.bind( keyObject );
QueryResults results = busobjOql.execute();
// There should be only one object found.
if ( results.hasMore() ) { Object obj = results.next();
return (BusinessObject) obj;
} else {
throw new ObjectNotFoundException(objectName, keyObject);
}
} catch (PersistenceException pe) { pe.printStackTrace();
throw new BlfException(pe.getMessage());
} }
This method took the query by primary key example discussed earlier and parame-terized it into an OQL string template. From the business object metadata, this method fills in the name of the business object and the key field property name. The run-time argument is bound to the query, and it is executed. A singular object is returned to the client, or an ObjectNotFoundException is thrown if no object is found. This exception is a subclass of BlfExceptionand is used as a standardized way to report
132 J2EE Best Practices: Java Design Patterns, Automation, and Performance
this condition. Usually, a search by primary key assumes the existence of an object, so you can regard this as an error condition. The ObjectNotFoundExceptioncode is shown here:
package blf;
public class ObjectNotFoundException extends BlfException { public ObjectNotFoundException(String objectName,
Object keyObject) { super(objectName + " object not found");
// Map to a standard application error.
setErrorList(ErrorList.createSingleErrorList(
"OBJ_NOT_FOUND", objectName, keyObject.toString()));
} }
You can then customize the definition of the OBJ_NOT_FOUNDerror message for your application. A basic definition might be defined as follows:
OBJ_NOT_FOUND=The {0} object with primary key {1} was not found.
You can see how the findByPrimaryKeyfactory method could also easily be implemented using straight JDBC as well. You could easily generate the SQL just like you generated the OQL. The reference architecture isolates this type of logic in the JDBCUtilityclass.
This example assumed that the object had an object identifier as a primary key field.
In most cases, this is the recommended approach for managing objects; however, you may have objects with a more complicated key structure. In lieu of a primary key object, you could use a value object to represent the key structure for the findByPrimaryKeymethod. You could also easily create a specificPrimaryKey base class to represent a key object. If you look at what that class would contain, it would have a set of properties that it would need to manage. This is the same thing that a value object does, and it has already been implemented. It generically manages a set of properties. You could use it for this purpose rather than create a bunch of spe-cific key classes for the business objects. Now, if you were using Entity Beans, you would still need that type of artifact (that is, a primary key object with explicit prop-erties) in order to use the component correctly. For the Java implementations, this is not necessary and the standard value object suffices. This approach may make the object purist a bit uncomfortable, and there may be a yearning for creating a subclass ofPrimaryKeyObject that extends ValueObject. This is certainly an option.
However, it requires you to convert value objects that come from the front end to a subclass in order to use them for this purpose. This is applicable, of course, only if you are using a value object approach to transport data between tiers. This topic will be discussed in detail in the next chapter on Service-Based Architecture. The eventual
N OT E
decision to have a specific primary key class is purely a design preference, and either choice works well. For the examples in this book, all of the objects have a single object identifier as a primary key.
A General Find Method
You would also like to be able to define different queries for a business object so that you can look up the object by non–key fields or combinations of values. This is analo-gous to additional finder methods being added to an EJB Home interface. The unique queries for a particular business object are defined by the WHERE clause portion of the SQL or OQL. According to the same principle of isolating JDBC and SQL from the application code, you can define these queries in the business object metadata.
As an example, say you want to look up an account based on the account number. The account number is an external identifier sometimes given as input data from the user as opposed to the primary key identifier in the database. You can define the following in the metadata:
<BusinessObject name="Account"
busObjClass="bank.castor.Account"
valueObjClass="bank.AccountData" >
<Property name="id" type="String"
required="true" key="true" autogen="true" />
<Property name="number" type="String"
required="true" />
<Property name="currentBalance" type="Currency"
required="true" />
<Property name="lastModifiedDate" type="Date" />
<Property name="type" type="String" required="true" />
<Collection name="byCustomer"
query="where customer = $1" />
<Collection name="byNumber" query="where number = $1" />
</BusinessObject>
A <Collection> tag was added that defines a WHERE clause for the particular query.
The collection has the name ‘byNumber’ so that you can refer to it in the call to the factory method. The factory appends this query to the base SELECT string, runs the query, and returns the populated business object. For queries that return multiple instances, there are a few options as to how you can handle these cases. You can throw an exception if more than one business object is found. You can also add an additional method to the BusinessObjectFactory such as findCollection, which returns a collection of business objects. This type of operation may be better handled by a collection service if you don’t want to deal with all of the results as business objects. Some operations are geared more toward running queries and possibly instan-tiating business objects from the results. This concept will be discussed in detail in the section on object collection services later in this chapter.
For the example, imagine you have an account search function in your application.
It might use the factory as follows to get a handle to an instance of the Account
134 J2EE Best Practices: Java Design Patterns, Automation, and Performance
business object identified by the particular account number:
ArrayList args = new ArrayList(1);
args.add(accountData.getProperty("number"));
Account account = (Account)
BusinessObjectFactory.find("Account",
"byNumber", args,
getDatabase());
This code snippet takes an account value object and uses the account number prop-erty as an argument to the query. The ‘byName’ string references the collection defined in the metadata. The number of arguments must match up with the number of refer-ences in the query’s WHERE clause. The factory could return a nullif not found or throw an exception if more than one customer record is found in this case.
You can also create a findCollectionmethod that returns a collection of busi-ness objects. An example of this would be if you want to retrieve the accounts for a given customer. The byCustomerquery was defined in the metadata that selects the accounts by customer. The following code snippet takes a customer value object and uses the identifier as an argument to the query. The factory returns a collection of business objects that matched the query.
ArrayList args = new ArrayList(1);
args.add(customerData.getProperty("id"));
Collection accountList =
BusinessObjectFactory.findCollection("Account",
"byCustomer", args);
The code to implement these two general find functions follows. The primary work is done infindCollection. The individualfindmethod simply delegates the call tofindCollectionand throws an exception if more than one object is found. This typically is considered an error condition, since the application using thefindmethod expects a single object in return. If no object is found, anullis returned. On the other hand, a search on some set of nonprimary key fields does not necessarily imply the existence of objects, so the application code can be allowed to directly handle this con-dition. Here is the code fromCastorFactoryImplfor these methods.
/*
* Discover an instance of a business object using the
* given query and arguments.
*/
public static BusinessObject find(String objectName, String queryId, ArrayList args, Database db) throws BlfException {
Object obj = null;
// Run the collection query and then // pick off the first element. There // should be only one object
// in the result set.
Collection coll =
findCollection(objectName,queryId,args,db);
if (coll.size() > 1) {
throw new BlfException("Multiple Objects Found", ErrorList.createSingleErrorList(
"MULTIPLE_OBJECTS_FOUND", objectName));
}
Iterator iter = coll.iterator();
if ( iter.hasNext() ) { obj = iter.next();
} else {
return null;
}
return (BusinessObject) obj;
} /*
* Discover a collection of business objects using the
* given query and arguments.
*/
public static Collection findCollection(String objectName, String queryId, ArrayList args, Database db)
throws BlfException {
// Obtain the business object metadata.
BusinessObjectMetadata bom =
MetadataManager.getBusinessObject(objectName);
// Create the result collection.
ArrayList results = new ArrayList();
try {
// Create the arguments for the // OQL string.
Object [] objArgs = new Object[1];
objArgs[0] = bom.getBusObjClass();
// Create a standard OQL string
// to look up with the given WHERE clause.
StringBuffer buffer = new StringBuffer(
MessageFormat.format(
"SELECT b FROM {0} b", objArgs));
buffer.append(' ');
buffer.append(bom.getQuery(queryId));
// Create the query and bind the // arguments.
String oqlString = buffer.toString();
OQLQuery busobjOql = db.getOQLQuery( oqlString );
136 J2EE Best Practices: Java Design Patterns, Automation, and Performance
Figure 4.2 EJB Business Object Interface Hierarchy.
<<interface>>
EntityLocal