Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g (10.1.3.1.0) Part Number B25947-01 |
|
|
View PDF |
When your business logic requires performing SQL queries, the natural choice is to use a view object to perform that task. Keep in mind that SQL statements you execute for validation will "see" pending changes in the entity cache if they are entity-based view objects; read-only view objects will only retrieve data that has been posted to the database.
Since entity objects are designed to be reused in any application scenario, they should not depend directly on a view object instance in any specific application module's data model. Doing so would prevent them from being reused in other application modules, which is highly undesirable.
Instead, your entity object can access the root application module in which it finds itself at runtime, and use that application module instances's createViewObject()
method to create an instance of the "validation" view object it requires. As with view object instances added to the data model at design time, this API allows you to assign an instance name to the view object so you can use findViewObject()
to find it again when needed.
Since the SQL-based validation code may be executed multiple times, it would not be the most efficient approach to create the view object instance each time it's needed and remove it when you are done using it. Instead, you can implement a helper method like what you see in Example 9-7 to use the view object instance if it already exists, or otherwise create it the first time it's needed. In order to ensure that the instance name of the runtime-created view object instance will not clash with the name of any design-time-specified ones in the data model, you can adopt the convention of constructing a name based on the view object definition name, prefixed by a string like "Validation_
". This is just one approach. As long as the name doesn't clash with a design time supplied name, you can use any naming scheme.
Example 9-7 Helper Method to Access View Object for Validation
/** * Find instance of view object used for validation purposes in the * root application module. By convention, the instance of the view * object will be named Validation_your_pkg_YourViewObject. * * If not found, create it for the first time. * * @return ViewObject to use for validation purposes * @param viewObjectDefName */ protected ViewObject getValidationVO(String viewObjectDefName) { // Create a new name for the VO instance being used for validation String name = "Validation_" + viewObjectDefName.replace('.', '_'); // Try to see if an instance of this name already exists ViewObject vo = getDBTransaction().getRootApplicationModule() .findViewObject(name); // If it doesn't already exist, create it using the definition name if (vo == null) { vo = getDBTransaction().getRootApplicationModule() .createViewObject(name,viewObjectDefName); } return vo; }
With a helper method like this in place, your validation code can call getValidationVO()
and pass it the fully qualified name of the view object definition that it wants to use. Then you can write code like what you see in Example 9-8.
Example 9-8 Using a Validation View Object in a Method Validator
// Sample entity-level validation method public boolean validateSomethingUsingViewObject() { Number numVal = getSomeEntityAttr(); String stringVal = getAnotherEntityAttr(); // Get the view object instance for validation ViewObject vo = getValidationVO("devguide.example.SomeViewObjectName"); // Set it's bind variables (which it will typically have!) vo.setNamedBindWhereClauseParam("BindVarOne",numVal); vo.setNamedBindWhereClauseParam("BindVarTwo",stringVal); vo.executeQuery(); if ( /* some condition */) { /* * code here returns true if the validation succeeds */ } return false; }
As the sample code suggests, view objects used for validation will typically have one or more named bind variables in them. Depending on the kind of data your view object retrieves, the "/* some condition */
" expression above will look different. For example, if your view object's SQL query is selecting a COUNT()
or some other aggregate, the condition will typically use the vo.first()
method to access the first row, then use the getAttribute()
method to access the attribute value to see what the database returned for the count.
If the validation succeeds or fails based on whether the query has returned zero or one row, the condition might simply test whether vo.first()
returns null
or not. If vo.first()
returns null
, there is no "first" row. In other words, the query retrieved no rows.
In other cases, you may be iterating over one or more query results retrieved by the view object to determine whether the validation succeeds or fails.
One common kind of SQL-based validation is a simple test that a candidate foreign key value exists in a related table. This type of validation can be implemented using the findByPrimaryKey()
method on the entity definition, however that will retrieve all attributes of the entity if the entity exists. An alternative approach to perform a lighter-weight existing check involves using a view object for validation.
Example 9-9 shows the exists()
method that the SRDemo application's Product
entity object contains in its custom entity definition class. First, it uses a variant of the helper method above that accepts a DBTransaction
as a parameter to return the instance of the appropriate validation view object. This is encapsulated inside the getProductExistsVO()
method in the same class.
This read-only view object used for validation is named ProductExistsById
in the oracle.srdemo.model.entities.validationqueries
package. Since this view object has a custom Java class (ProductExistsByIdImpl
), the code in the exists()
method can use the strongly-typed setTheProductId()
method to set the named bind variable that the view object defines. Then the code executes the query and sets the boolean foundIt
variable based on whether a row was found, or not.
Example 9-9 Efficient Existence Check Using View Object and Entity Cache
// In ProductDefImpl.java in SRDemo sample public boolean exists(DBTransaction t, Number productId) { boolean foundIt = false; ProductExistsByIdImpl vo = getProductExistsVO(t); vo.setTheProductId(productId); vo.setForwardOnly(true); vo.executeQuery(); foundIt = (vo.first() != null); /* * If we didn't find it in the database, * try checking against any new employees in cache */ if (!foundIt) { Iterator iter = getAllEntityInstancesIterator(t); while (iter.hasNext()) { ProductImpl product = (ProductImpl)iter.next(); /* * NOTE: If you allow your primary key attribute to be modifiable * then you should also check for entities with entity state * of Entity.STATUS_MODIFIED here as well. */ if (product.getEntityState() == Entity.STATUS_NEW && product.getProdId().equals(productId)) { foundIt = true; break; } } } return foundIt; }
Even though the SRDemo application currently does not allow the end user to create new products, it's good to implement the validation in a way that assumes the user might be able to do this in some future screens that are implemented. The code that follows the executeQuery()
tests to see whether the candidate product ID is for a new Product
entity that exists in the cache.
Recall that since the validation view object has no entity usages, its query will only "see" rows that are currently in the database. So, if the foundIt
variable is false after trying the database, the remaining code gets an iterator for the ProductImpl
entity rows that are currently in the cache and loops over them to see if any new Product
entity row has the candidate product ID. If it does, the exists()
method still returns true
.
The beforeCommit()
method is invoked on each entity row in the pending changes list after the changes have been posted to the database, but before they are committed. This can be a perfect method in which to execute view object-based validations that must assert some rule over all entity rows of a given type.
Note: If yourbeforeCommit() logic can throw a ValidationException , you must set the jbo.txn.handleafterpostexc property to true in your configuration to have the framework automatically handle rolling back the in-memory state of the other entity objects that may have already successfully posted to the database (but not yet been committed) during the current commit cycle. |