Skip Headers
Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers
10g (10.1.3.1.0)

Part Number B25947-01
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Master Index
Master Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

7.7 Understanding How View Objects and Entity Objects Cooperate at Runtime

On their own, view objects and entity objects simplify two important jobs that every enterprise application developer needs to do:

Entity-based view objects can query any selection of data your end user needs to see or modify. Any data they are allowed to change is validated and saved by your reusable business domain layer. The key ingredients you provide as the developer are the ones that only you can know:

These are the things that make your application unique. The built-in functionality of your entity-based view objects handle the rest of the implementation details. You've experimented above with entity-based view objects in the Business Components Browser and witnessed some of the benefits they offer, but now it's time to understand exactly how they work. This section walks step by step through a scenario of retrieving and modifying data through an entity-based view object, and points out the interesting aspects of what's going on behind the scenes. But before diving in deep, you need a bit of background on row keys and on what role the entity cache plays in the transaction, after which you'll be ready to understand the entity-based view object in detail.

7.7.1 Each View Row or Entity Row Has a Related Key

As shown in Figure 7-21, when you work with view rows you use the Row interface in the oracle.jbo package. It contains a method called getKey() that you can use to access the Key object that identifies any row. Notice that the Entity interface in the oracle.jbo.server package extends the Row interface. This relationship provides a concrete explanation of why the term entity row is so appropriate. Even though an entity row supports additional features for encapsulating business logic and handling database access, you can still treat any entity row as a Row.

Recall that both view rows and entity rows support either single-attribute or multi-attribute keys, so the Key object related to any given Row will encapsulate all of the attributes that comprise its key. Once you have a Key object, you can use the findByKey() method on any row set to find a row based on its Key object.

Figure 7-21 Any View Row or Entity Row Supports Retrieving Its Identifying Key

Image shows how rows retrieve their identifying keys

Note:

When you define an entity-based view object, by default the primary key attributes for all of its entity usages are marked with their Key Attribute property set to true. It is best practice to subsequently disable the Key Attribute property for the key attributes from reference entity usages. Since view object attributes related to the primary keys of updatable entity usages must be part of the composite view row key, their Key Attribute property cannot be disabled.

7.7.2 What Role Does the Entity Cache Play in the Transaction

An application module is a transactional container for a logical unit of work. At runtime, it acquires a database connection using information from the named configuration you supply, and it delegates transaction management to a companion Transaction object. Since a logical unit of work may involve finding and modifying multiple entity rows of different types, the Transaction object provides an entity cache as a "work area" to hold entity rows involved in the current user's transaction. Each entity cache contains rows of a single entity type, so a transaction involving both the User and ServiceHistory entity objects holds the working copies of those entity rows in two separate caches.

By using an entity object's related entity definition, you can write code in an application module to find and modify existing entity rows. As shown in Figure 7-22, by calling findByPrimaryKey() on the entity definition for the ServiceRequest entity object, you can retrieve the row with that key. If it is not already in the entity cache, the entity object executes a query to retrieve it from the database. This query selects all of the entity object's persistent attributes from its underlying table, and find the row using an appropriate WHERE clause against the column corresponding to the entity object's primary key attribute. Subsequent attempts to find the same entity row by key during the same transaction will find it in the cache, avoiding a trip to the database. In a given entity cache, entity rows are indexed by their primary key. This makes finding and entity row in the cache a fast operation.

When you access related entity rows using association accessor methods, they are also retrieved from the entity cache, or are retrieved from the database if they are not in the cache. Finally, the entity cache is also the place where new entity rows wait to be saved. In other words, when you use the createInstance2() method on the entity definition to create a new entity row, it is added to the entity cache.

Figure 7-22 During the Transaction ServiceRequest, Entity Rows are Stored In ServiceRequest Entity Cache

Image shows how rows are stored in entity cache

When an entity row is created, modified, or removed, it is automatically enrolled in the transaction's list of pending changes. When you call commit() on the Transaction object, it processes its pending changes list, validating new or modified entity rows that might still be invalid. When the entity rows in the pending list are all valid, the Transaction issues a database SAVEPOINT and coordinates saving the entity rows to the database. If all goes successfully, it issues the final database COMMIT statement. If anything fails, the Transaction performs a ROLLBACK TO SAVEPOINT to allow the user to fix the error and try again.

The Transaction object used by an application module represents the working set of entity rows for a single end-user transaction. By design, it is not a shared, global cache. The database engine itself is an extremely efficient shared, global cache for multiple, simultaneous users. Rather than attempting to duplicate the 30+ years of fine-tuning that has gone into the database's shared, global cache functionality, ADF Business Components consciously embraces it. To refresh a single entity object's data from the database at any time, you can call its refresh() method. You can setClearCacheOnCommit() or setClearCacheOnRollback() on the Transaction object to control whether entity caches are cleared at commit or rollback. The defaults are false and true, respectively. The Transaction object also provides a clearEntityCache() method you can use to programmatically clear entity rows of a given entity type (or all types). By clearing an entity cache, entity rows of that type will be retrieved from the database fresh the next time they are found by primary key, or retrieved by an entity-based view object, as you'll see in the following sections.

7.7.3 Metadata Ties Together Cleanly Separated Roles of Data Source and Data Sink

When you want to venture beyond the world of finding an entity row by primary key and navigating related entities via association accessors, you turn to the entity-based view object to get the job done. In an entity-based view object, the view object and entity object play cleanly separated roles:

  • The view object is the data source: it retrieves the data using SQL.

  • The entity object is the data sink: it handles validating and saving data changes.

Because view objects and entity objects have cleanly separated roles, you can build a hundred different view objects — projecting, filtering, joining, sorting the data in whatever way your user interfaces require application after application — without any changes to the reusable entity object. In fact, in some larger development organizations, the teams responsible for the core business domain layer of entity objects might be completely separate from the ones who build specific application modules and view objects to tackle an end-user requirement. This extremely flexible, symbiotic relationship is enabled by metadata an entity-based view object encapsulates about how the SELECT list columns related to the attributes of one or more underlying entity objects.

Imagine a new requirement arises where your end users are demanding a page to quickly see open and pending service requests. They want to see only the service request ID, status, and problem description; the technician assigned to resolve the request; and the number of days the request has been open. It should be possible to update the status and the assigned technician. Figure 7-23 shows a new entity-based view object named OpenProblemsAndAssignees that can support this new requirement.

The dotted lines in the figure represent the metadata captured in the view object's XML component definition that maps SELECT list columns in the query to attributes of the entity objects used in the view object.

A few things to notice about the view object and its query are:

  • It joins data from a primary entity usage (ServiceRequest) with that from a secondary reference entity usage (User), based on the association related to the assigned technician you've seen in examples above

  • It's using an outer join of ServiceRequest.ASSIGNED_TO = Technician.USER_ID (+)

  • It includes a SQL-calculated attribute DaysOpen based on the SQL expression CEIL(SYSDATE - TRUNC(REQUEST_DATE))

Figure 7-23 View Object Encapsulates a SQL Query and Entity Attribute Mapping Metadata

Image of how view object encapsulates queries and metadata

7.7.4 What Happens When a View Object Executes Its Query

After adding an instance of OpenProblemsAndAssignees with the same name to the SRService's data model, you can see what happens at runtime when you execute the query. Like a read-only view object, an entity-based view object sends its SQL query straight to the database using the standard Java Database Connectivity (JDBC) API, and the database produces a result set. In contrast to its read-only counterpart, however, as the entity-based view object retrieves each row of the database result set, it partitions the row attributes based on which entity usage they relate to. This partitioning occurs by creating an entity object row of the appropriate type for each of the view object's entity usages, populating them with the relevant attributes retrieved by the query, and storing each of these entity rows in its respective entity cache. Then, rather than storing duplicate copies of the data, the view row simply points at the entity row parts that comprise it. As shown in Figure 7-24, the highlighted row in the result set is partitioned into a User entity row with primary key 306 and a ServiceRequest entity row with primary key 112. Since the SQL-calculated DaysOpen attribute is not related to any entity object, its value is stored directly in the view row.

The ServiceRequest entity row that was brought into the cache above using findByPrimaryKey() contained all attributes of the ServiceRequest entity object. In contrast, a ServiceRequest entity row created by partitioning rows from the OpenProblemsAndAssignees query result contains values only for attributes that appear in the query. It does not include the complete set of attributes. This partially populated entity row represents an important runtime performance optimization.

Since the ratio of rows retrieved to rows modified in a typical enterprise application is very high, bringing only the attributes into memory that you need to display can represent a big memory savings over bringing all attributes into memory all the time.

Finally, notice that in the queried row for service request 114 there is no assigned technician, so in the view row it has a null entity row part for its User entity object.

Figure 7-24 View Rows Are Partitioned into Entity Rows in Entity Caches

Image shows how view rows are partitioned

By partitioning queried data this way into its underlying entity row constituent parts, the first benefit you gain is that all of the rows that include some data queried about the user with UserId = 306 will display a consistent result when changes are made in the current transaction. In other words, if one view object allows the Email attribute of user 306 to be modified, then all rows in any entity-based view object showing the Email attribute for user 306 will update instantly to reflect the change. Since the data related to user 306 is stored exactly once in the User entity cache in the entity row with primary key 306, any view row that has queried the user's Email attribute is just pointing at this single entity row.

Luckily, these implementation details are completely hidden from a client working with the rows in a view object's row set. Just as you did in the Business Components Browser, the client works with a view row, getting and setting the attributes, and is unaware of how those attributes might be related to entity rows behind the scenes.

7.7.5 What Happens When You Modify a View Row Attribute

You see above that among other rows, the OpenProblemsAndAssignees result set includes a row related to service request 112. When a client attempts to update the status of service request 112 to the value Closed, ultimately a setStatus("Closed") method gets called on the view row. Figure 7-25 illustrates the steps that will occur to automatically coordinate this view row attribute modification with the underlying entity row:

  1. The client attempts to set the Status attribute to the value Closed

  2. Since Status is an entity-mapped attribute from the ServiceRequest entity usage, the view row delegates the attribute set to the appropriate underlying entity row in the ServiceRequest entity cache having primary key 112.

  3. Any attribute-level validation rules on the Status attribute at the ServiceRequest entity object get evaluated and will fail the operation if they don't succeed.

    Assume that some validation rule for the Status attribute programmatically references the RequestDate attribute (for example, to enforce a business rule that a ServiceRequest cannot be closed the same day it is opened). The RequestDate was not one of the ServiceRequest attributes retrieved by the query, so it is not present in the partially populated entity row in the ServiceRequest entity cache.

  4. To ensure that business rules can always reference all attributes of the entity object, the entity object detects this situation and "faults-in" the entire set of ServiceRequest entity object attributes for the entity row being modified using the primary key (which must be present for each entity usage that participates in the view object.

  5. After the attribute-level validations all succeed, the entity object attempts to acquire a lock on the row in the SERVICE_REQUESTS table before allowing the first attribute to be modified.

  6. If the row can be locked, the attempt to set the Status attribute in the row succeeds and the value is changed in the entity row.


Note:

The jbo.locking.mode configuration property controls how rows are locked. The default value is pessimistic, whose behavior corresponds to the steps described here. In pessimistic locking mode, the row must be lockable before any change is allowed to it in the entity cache. Typically, web applications will set this property to optimistic instead, so that rows aren't locked until transaction commit time.

Figure 7-25 Updating a View Row Attribute Delegates to Entity

Image shows how view rows delegate to entity

7.7.6 What Happens When You Change a Foreign Key Attribute

If the user also updates the technician assigned to service request 112, then something else interesting occurs. The request is currently assigned to vpatabal, who has user ID 306. Assume that the end user sets the AssignedTo attribute to 300 to reassign the request to sking. As shown in Figure 7-26, behind the scenes, the following occurs:

  1. The client attempts to set the AssignedTo attribute to the value 300.

  2. Since AssignedTo is an entity-mapped attribute from the ServiceRequest entity usage, the view row delegates the attribute set to the appropriate underlying entity row in the ServiceRequest entity cache having primary key 112.

  3. Any attribute-level validation rules on the AssignedTo attribute at the ServiceRequest entity object get evaluated and will fail the operation if they don't succeed.

  4. The row is already locked, so the attempt to set the AssignedTo attribute in the row succeeds and the value is changed in the entity row.

  5. Since the AssignedTo attribute on the ServiceRequest entity usage is associated to the reference entity usage named Technician to the User entity object, this change of foreign key value causes the view row to replace its current entity row part for user 306 with the entity row corresponding to the new UserId = 300. This effectively makes the view row for service request 112 point to the entity row for sking, so the value of the Email in the view row updates to reflect the correct reference information for this newly assigned technician.

Figure 7-26 After Updating a Foreign Key, View Row Points to a New Entity

Image shows foreign key pointing to new entity after update

7.7.7 What Happens When You Re-query Data

When you reexecute a view object's query, by default the view rows in its current row set are "forgotten" in preparation for reading in a fresh result set. This view object operation does not directly affect the entity cache, however. The view object then sends the SQL to the database and the process begins again to retrieve the database result set rows and partition them into entity row parts.


Note:

Typically when you re-query, you are doing it in order to see the latest database information. If instead you want to avoid a database roundtrip by restricting your view object to querying only over existing entity rows in the cache, or over existing rows already in the view object's row set, Section 27.5, "Performing In-Memory Sorting and Filtering of Row Sets" explains how to do this.

7.7.7.1 Unmodified Attributes in Entity Cache are Refreshed During Re-query

As part of this entity row partitioning process during a re-query, if an attribute on the entity row is unmodified, then its value in the entity cache is updated to reflect the newly queried value.

7.7.7.2 Modified Attributes in Entity Cache are Left Intact During Re-query

If the value of an entity row attribute has been modified in the current transaction, then during a re-query the entity row partitioning process does not refresh its value. Uncommitted changes in the current transaction are left intact so the end-user's logical unit of work is preserved. As with any entity attribute value, these pending modifications continue to be consistently displayed in any entity-based view object rows that reference the modified entity rows.

Figure 7-27 illustrates this scenario. Imagine that in the context of the current transaction's pending changes, a user "drills down" to a different page that uses the ServiceRequests view object instance to retrieve all details about service request 112. That view object has four entity usages: a primary ServiceRequest usage, and three reference usages for Product, User (Technician), and User (Customer). When its query result is partitioned into entity rows, it ends up pointing at the same ServiceRequest entity row that the previous OpenProblemsAndAssignees view row had modified. This means the end user will correctly see the pending change, that the service request is assigned to Steven King in this transaction.

Figure 7-27 Overlapping Sets of Entity Attributes from Different View Objects are Merged in Entity Cache

Image of how different view objects are merged in cache

7.7.7.3 Overlapping Subsets of Attributes are Merged During Re-query

Figure 7-27 also illustrates the situation that the ServiceRequests view object's query retrieves a different subset of reference information about users than the OpenProblemsAndAssignees did. The ServiceRequests queries up FirstName and LastName for a user, while the OpenProblemsAndAssignees view object queried the user's Email. The figure shows what happens at runtime in this scenario. If while partitioning the retrieved row, the entity row part contains a different set of attributes than the partially populated entity row that is already in the cache, the attributes get "merged". The result is a partially populated entity row in the cache with the union of the overlapping subsets of user attributes. In contrast, for John Chen (user 308) who wasn't in the cache already, the resulting new entity row contains only the FirstName and LastName attributes, but not the Email.

7.7.8 What Happens When You Commit the Transaction

Suppose the user is happy with her changes, and commits the transaction. As shown in Figure 7-28, there are two basic steps:

  1. The Transaction object validates any invalid entity rows in its pending changes list.

  2. The entity rows in the pending changes list are saved to the database.

The figure depicts a loop in step 1 before the act of validating one modified entity object might programmatically affect changes to other entity objects. Once the transaction has processed its list of invalid entities on the pending changes list, if the list is still nonempty, it will complete another pass through the list of invalid ones. It will attempt up to ten passes through the list. If by that point there are still invalid entity rows, it will throw an exception since this typically means you have an error in your business logic that needs to be investigated.

Figure 7-28 Committing the Transaction Validates Invalid Entities, Then Saves Them

Image of how transaction validates invalid entities

7.7.9 Interactively Testing Multiuser Scenarios

The last aspect to understand about how view objects and entity objects cooperate involves two exceptions that can occur when working in a multiuser environment. Luckily, these are easy to simulate for testing purposes by simply starting up the Business Components Browser two times on the SRService application module (without exiting from the first instance of course). Try the following two tests to see how these multiuser exceptions can arises:

  • In one Business Components Browser tester modify the status of an existing service request and tab out of the field. Then, in the other Business Components Browser window, try to modify the same service request in some way. You'll see that the second user gets the oracle.jbo.AlreadyLockedException

    Try repeating the test, but after overriding the value of jbo.locking.mode to be optimistic on the Properties page of the Business Components Browser Connect dialog. You'll see the error occurs at commit time for the second user instead of immediately.

  • In one Business Components Browser tester modify the status of an existing service request and tab out of the field. Then, in the other Business Components Browser window, retrieve (but don't modify) the same status request. Back in the first window, commit the change. If the second user then tries to modify that same service request, you'll see that the second user gets the oracle.jbo.RowInconsistentException. The row has been modified and committed by another user since the second user retrieved the row into the entity cache