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

Part Number B25947-02
Go to Documentation Home
Home
Go to Table of Contents
Contents
Go to Index
Index
Go to Feedback page
Contact Us

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

7 Building an Updatable Data Model With Entity-Based View Objects

This chapter describes how to build updatable view objects that cooperate automatically with entity objects to enable a fully updatable data model.

This chapter includes the following sections:

7.1 Introduction to Entity-Based View Objects

An entity-based view object supports updatable rows. The view object queries just the data needed for the client-facing task at hand, then cooperates with one or more entity objects in your business domain layer to automatically validate and save changes made to its view rows. Like the read-only view object, an entity-based view object encapsulates a SQL query, can be linked into master/detail hierarchies, and can be used in the data model of your application modules.

By the end of this chapter, you will understand the concepts shown in Figure 7-1:

This chapter explains how instances of entity-based view objects in an application module's data model enable clients to search for, update, insert, and delete business domain layer information in a way that combines the full data shaping power of SQL with the clean, object-oriented encapsulation of reusable, domain business objects. And all without requiring a line of code.

Figure 7-1 View Objects and Entity Objects Collaborate to Enable an Updatable Data Model

Image of how objects create updatable data model

Note:

The examples in this chapter use the same basic SRDemo application business domain layer of ServiceRequest, ServiceHistory Product, User, and ExpertiseArea entity objects from Chapter 6, "Creating a Business Domain Layer Using Entity Objects". To experiment with a working version of the examples, download the DevGuideExamples workspace from the Example Downloads page at http://otn.oracle.com/documentation/jdev/b25947_01 and see the EntityBasedViewObjects project.

7.2 Creating an Entity-Based View Object

Creating an entity-based view object is even easier than creating a read-only view object, since you don't have to type in the SQL statement yourself. An entity-based view object also offers significantly more runtime functionality than its read-only counterpart.

7.2.1 How to Create an Entity-Based View Object

To create an entity-based view object, use the Create View Object wizard. The wizard is available from the New Gallery in the Business Tier > ADF Business Components category. Assume that you want to create a StaffList view object in the devguide.model.queries package to retrieve an updatable list of staff members.

As shown in Figure 7-2, in step 1 on the Name page, provide the view object's name and package. Keep the default setting to manage data with Updatable Access through Entity Objects.

Figure 7-2 Providing a Name and Package for an Updatable View Object

Image of Step 1 of the Create View Object wizard

In step 2 on the Entity Objects page, select the entity object whose data you want to use in the view object. Figure 7-3 shows the result after selecting the User entity object and shuttling it into the Selected list. An entry in this list is known as an entity usage — since it records the entity objects that the view object will be using. It could also be thought of as an entity reference, since the view object references attributes from that entity.

Figure 7-3 Selecting the Entity Objects Whose Data You Want to Include in the View Object

Image of step 2 of the Create View Object wizard

In step 3 on the Attributes page, select the attributes you want to include from the entity usage in the Available list and shuttle them to the Selected list. In Figure 7-4, the UserId, Email, FirstName, and LastName attributes have been selected.

Figure 7-4 Selecting the Entity Attributes Whose Data You Want to Include in the View Object

Image of step 3 of the Create View Object wizard

In step 4 on the Attribute Settings page, you can use the Select Attribute dropdown to switch between the view object attributes in order to change their names or any of their initial settings. For this example, you can accept the defaults.

In step 5 on the SQL Statement page, as shown in Figure 7-5, JDeveloper automatically generates the SELECT statement based on the entity attributes you've selected. You can add a WHERE and ORDER BY clause to the query to filter and order the data as required. Since this StaffList view object should display only the rows in the USERS table that have a value of technician or manager for the USER_ROLE column, you can include an appropriate WHERE clause predicate in the Where field. To order the data by last name and first name, included an appropriate ORDER BY clause in the Order By field. Notice that the Where and Order By field values appear without the WHERE or ORDER BY keyword. The view object adds those keywords at runtime when it executes the query.

Figure 7-5 Adding Custom Where and Order By Clauses to the Generated SQL Statement

Image of step 5 of the Create View Object wizard

Click Finish at this point to create the view object.

7.2.1.1 Creating a View Object Having All Attributes of an Entity Object

When you want to allow the client to work with all of the attributes of an underlying entity object, you can use the Create View Object wizard as described in Section 7.2.1, "How to Create an Entity-Based View Object". After selecting the entity object, simply select all of its attributes on the Attributes page. However, for this frequent operation, there is an even quicker way to perform the same task in the Application Navigator.

To create a new entity-based view object:

  1. Select the desired entity object in the Application Navigator.

  2. Choose New Default View Object... from the context menu.

  3. Provide a package and component name for the new view object in the Create Default View Object dialog, as shown in Figure 7-6.

Figure 7-6 Shortcut to Creating a Default View Object for an Entity Object

Image of Create Default View Object dialog

The new entity-based view object created will be identical to one you could have created with the Create View Object wizard. By default, it will have a single entity usage referencing the entity object you selected in the Application Navigator, and will include all of its attributes. It will initially have neither a WHERE nor ORDER BY clause, and you may want to use the View Object Editor to:

  • Remove unneeded attributes

  • Refine its selection with a WHERE clause

  • Order its results with an ORDER BY clause

  • Customize any of the view object properties

7.2.2 What Happens When You Create an Entity-Based View Object

When you create an entity-based view object, JDeveloper creates the XML component definition file that represents the view object's declarative settings and saves it in the directory that corresponds to the name of its package. In Figure 7-2, the view object was named StaffList in the devguide.model.queries package, so the XML file created will be ./devguide/model/queries/StaffList.xml under the project's source path. This XML file contains the information about the SQL query, the name of the entity usage, and the properties of each attribute. If you're curious to see its contents, you can see the XML file for the view object by selecting the view object in the Application Navigator and looking in the corresponding Sources folder in the Structure Window. Double-clicking on the StaffList.xml node will open the XML in an editor so you can inspect it.

Note:

If your IDE-level Business Components Java generation preferences so indicate, the wizard may also create an optional custom view object class StaffListImpl.java and/or a custom view row class StaffListRowImpl.java class.

7.2.3 Editing an Existing Entity-Based View Object Definition

After you've created an entity-based view object, you can edit any of its settings by using the View Object Editor. Select the Edit menu option on the context menu in the Application Navigator, or double-click on the view object, to launch the view object editor. By opening the different panels of the editor, you can adjust the WHERE and ORDER BY clause of the query, change the attribute names, add named bind variables, add UI controls hints, control Java generation options, and configure other settings.

7.2.4 What You May Need to Know About View Objects

Each view object attribute inherits the properties of the corresponding entity object attribute.

7.2.4.1 View Object Attributes Inherit Properties from Underlying Entity Object Attributes

One interesting aspect of entity-based view objects is that each attribute that relates to an underlying entity object attribute inherits that attributes properties. Figure 7-7 shows the View Object Editor with the UserId attribute selected. You can see that properties like the Java Attribute Type and the Query Column Type are disabled and their values are inherited from the related UserId attribute of the User entity object to which this view object is related. Some properties like the attribute's datatype are inherited and cannot be changed at the view object level.

Other properties like Queryable and Updatable are inherited but can be overridden as long as their overridden settings are more restrictive than the inherited settings. For example, the UserId attribute in the User entity object has an Updatable setting of Always. As shown Figure 7-7, the View Object Editor would allow you to set the corresponding view object attribute to a more restrictive setting like While New or Never. However, if the UserId attribute in the User entity object had instead an Updatable setting of Never, then the editor would not allow the StaffList's related view object attribute to have a less restrictive setting like Always.

Figure 7-7 View Object Attributes Inherit Properties from Underlying Entity Object Attributes

Image of View Object editor

7.3 Including Reference Entities in Join View Objects

It is extremely common in business applications to supplement information from a primary business domain object with secondary reference information to help the end user understand what foreign key attributes represent. Take the example of the ServiceRequest entity object. It contains foreign key attributes of type Number like:

From experience, you know that showing an end user exclusively these "raw" numerical values won't be very helpful. Ideally, reference information from the related User and Product entity objects should be displayed to improve the application's usability. One typical solution involves performing a join query that retrieves the combination of the primary and reference information. Alternatively, developers populate "dummy" fields in each queried row with reference information based on extra queries against the lookup tables. When the end user can change the foreign key values as she edits the data, this presents an additional challenge.

For example, when reassigning a service request from one technician to another, the end user expects the reference information to stay in sync. Luckily, entity-based view objects support easily including reference information that's always up to date. The key requirement to leverage this feature is the presence of associations between the entity object that act as the view object's primary entity usage and the entity objects that contribute reference information.

This section describes how to modify the default ServiceRequests view object created above to include reference information from the User and Product entity objects.

7.3.1 How to Include Reference Entities in a View Object

To include reference entities in a view object, open the View Object Editor on an entity-based view object that already has a single entity usage, and open the Entity Objects page. The first entity usage in the Selected list on this page is known as the primary entity usage for the view object. The list is not limited to a single entity usage, however. To use additional entity objects in the view object, select them in the Available list and shuttle them to the Selected list.

7.3.1.1 Adding Additional Reference Entity Usages to the View Object

Figure 7-8 shows the result of adding three additional reference entity usages to the existing ServiceRequests view object: one for the Product and two separate usages of the User entity. When you click on an entity usage in the Selected list, the state of the Reference checkbox indicates that the second and subsequent entity usages added to a view object are marked as reference information by default. Similarly, the secondary entity usages default to being not updatable, as the unchecked state of their Updatable checkbox confirms.

Note:

When adding secondary entity usages, they default to having their Updatable flag false and their Reference flag true. This is by far the most common usage pattern. In Section 27.9, "Creating a View Object with Multiple Updatable Entities", you'll explore the less common, yet still useful, situation of having a join view object with multiple, updatable entity usages.

The Association dropdown list shows you the name of the association that relates the selected entity usage to the primary one. The Alias field allows you to give a more meaningful name to the entity usage when the default name is not clear. For example, after shuttling two entity usages for the User entity object into the Selected list, initially the alias for the usages was User1 and User2. You can see in the figure that renaming these to be Technician and Customer instead greatly clarifies what reference information they are contributing to the view object. Importantly, the figure also illustrates that when you add multiple entity usages for the same entity, you need to use the Association dropdown list to select which association represents that usage's relationship to the primary entity usage. For the Technician entity usage select the ServiceRequestsAssignedToUser association, and for the Customer entity usage select the ServiceRequestsCreatedByUser association.

Figure 7-8 Indicating Correct Associations for Multiple Reference Entity Usages

Image shows adding reference entity usages in view object

7.3.1.2 Selecting Additional Attributes from Reference Entity Usages

After adding these secondary entity usages, switch to the Attributes page of the View Object Editor and select the specific, additional attributes from these new usages that you want to include in the view object. Figure 7-9 illustrates the result of shuttling the following extra attributes into the Selected list:

  • The Name attribute from the Product entity usage

  • The Email attribute from the Technician entity usage

  • The Email attribute from the Customer entity usage

Notice that even if you didn't intend to include them, JDeveloper automatically verifies that the primary key attribute from each entity usage is part of the Selected list. If it's not already present in the list, JDeveloper adds it for you.

Figure 7-9 Selecting Additional Reference Entity Attributes to Include in the View Object

Images of shuttling extra attributes to the Selected list

Selecting the SQL Statement page, you can see that JDeveloper has included the new columns in the SELECT statement and has updated the Where field to include the appropriate join clauses of:

((ServiceRequest.PROD_ID = Product.PROD_ID) AND
 (ServiceRequest.ASSIGNED_TO = Technician.USER_ID)) AND
 (ServiceRequest.CREATED_BY = Customer.USER_ID)

7.3.1.3 Renaming Attributes from Reference Entity Usages

Expanding the Attributes node in the tree at the left of the View Object Editor, you can see the additional attributes are added at the end of the list. Since the default attribute names are not as clear as they could be, by selecting each one in turn, you can rename them as follows:

  • Name -> ProductName

  • ProdId1 -> PKProdId (Primary Key from Product entity usage)

  • Email -> TechnicianEmail

  • UserId -> PKTechnicianUserId (Primary Key from Technician entity usage)

  • Email1 -> CustomerEmail

  • UserId1 -> PKCustomerUserId (Primary Key from Customer entity usage)

7.3.1.4 Removing Unnecessary Key Attributes from Reference Entity Usages

The view object attribute corresponding to the primary key attribute of the primary entity usage acts at the primary key for identifying the view row. When you add secondary entity usages, JDeveloper also marks the view object attributes corresponding to their primary key attributes as part of the view row key as well. When your view object consists of a single updatable primary entity usage and a number of reference entity usages, the primary key attribute from the primary entity usage already is enough to uniquely identify the view row. These additional key attributes are unneeded and you should toggle their Key Attribute setting to false. For the view object created above, toggle this setting to false for the following attributes so that Key Attribute is no longer checked: PKProdId, PKTechnicianUserId, and PKCustomerUserId.

7.3.1.5 Hiding the Primary Key Attributes from Reference Entity Usages

Since you generally won't want to display the primary key attributes that were automatically added to the view object, you can set their Display Hint property on the UI Control Hints page to Hide as shown in Figure 7-10.

Figure 7-10 Setting an Attribute Control Hint to Hide Primary Key Attributes from Reference Entity Usages

Image of View Object Editor for ServiceRequests

Click OK to save your changes to the view object.

7.3.2 What Happens When You Reference Entities in a View Object

When you include secondary entity usages by reference in a view object, JDeveloper updates the view object's XML component definition to include information about the additional entity usages. For example, if you look at the ServiceRequests.xml file for the view object that was enhanced above to include three additional reference entity usages, you will see this information recorded in the multiple <EntityUsage> elements in that file. For example, you'll see an entry for the primary entity usage like this:

<EntityUsage
   Name="ServiceRequest"
   Entity="devguide.model.entities.ServiceRequest"/>

The secondary reference entity usages will have a slightly different entry like this, including information about the association that relates it to the primary entity usage:

<EntityUsage
   Name="Product"
   Entity="devguide.model.entities.Product"
   Association=
    "devguide.model.entities.associations.ServiceRequestsForProduct"
   AssociationEnd=
    "devguide.model.entities.associations.ServiceRequestsForProduct.Product"
   SourceUsage="devguide.model.queries.ServiceRequests.ServiceRequest"
   ReadOnly="true"
   Reference="true"/>

Each attribute entry in the XML indicates which entity usage it references. This entry for the ProblemDescription attribute shows that it's related to the ServiceRequest entity usage:

<ViewAttribute
      Name="ProblemDescription"
      IsNotNull="true"
      EntityAttrName="ProblemDescription"
      EntityUsage="ServiceRequest"
      AliasName="PROBLEM_DESCRIPTION" >
   </ViewAttribute>

While the CustomerEmail attribute is related to the Customer entity usage.

<ViewAttribute
      Name="CustomerEmail"
      IsUpdatable="false"
      IsNotNull="true"
      EntityAttrName="Email"
      EntityUsage="Customer"
      AliasName="EMAIL1" >
   </ViewAttribute>

The View Object Editor uses this association information at design time to automatically build the view object's join WHERE clause. It uses the information at runtime to enable keeping the reference information up to date when the end user changes foreign key attribute values.

7.3.3 What You May Need to Know About Join View Objects

{para}?>If your view objects reference multiple entity objects, these are displayed as separate entity usages on a business components diagram. Note that you can also modify the default inner join clause to be an outer join when appropriate.

7.3.3.1 Showing View Objects in a Business Components Diagram

Section 6.4, "Creating an Entity Diagram for Your Business Layer" explained how to create a Business Components Diagram to visualize your business domain layer. In addition to supporting entity objects, JDeveloper's UML diagramming support allows you to drop view objects onto diagram as well to visualize their structure and entity usages. If you create a new Business Components Diagram named SRService Data Model in the devguide.model.design package, and drag the ServiceRequests view object from the Application Navigator onto the diagram, you'll see what's shown in Figure 7-11. When viewed as an expanded node, the diagram shows a compartment containing the view objects entity usages.

Figure 7-11 View Object and Its Entity Usages in a Business Components Diagram

Image of business components diagram for object usages

7.3.3.2 Modify Default Join Clause to Be Outer Join When Appropriate

When JDeveloper creates the WHERE clause for the join between the table for the primary entity usage and the tables for secondary entity usages that are related to it, by default it always creates inner joins. Study the WHERE clause of the ServiceRequests view object more closely:

((ServiceRequest.PROD_ID = Product.PROD_ID) AND
 (ServiceRequest.ASSIGNED_TO = Technician.USER_ID)) AND
 (ServiceRequest.CREATED_BY = Customer.USER_ID)

When service requests are not yet assigned to a technician, their AssignedTo attribute will be null. The default inner join condition generated above will not retrieve these unassigned service requests. Assuming that you want unassigned service requests to be viewable and updatable through the ServiceRequests view object, you'll need to revisit the SQL Statement page of the View Object Editor to change the query into an outer join to the USER table for the possibly null ASSIGNED_TO column value. The updated WHERE clause shown below includes the additional (+) operator on the side of the equals sign for the related table whose data is allowed to be missing in the join:

((ServiceRequest.PROD_ID = Product.PROD_ID) AND
 (ServiceRequest.ASSIGNED_TO = Technician.USER_ID (+) )) AND
 (ServiceRequest.CREATED_BY = Customer.USER_ID)

7.4 Creating an Association-Based View Link

Just as with read-only view objects, you can link entity-based view objects to other view objects to form master/detail hierarchies of any complexity. The only difference in the creation steps involves the case when both the master and detail view objects are entity-based view objects and their respective entity usages are related by an association. In this situation, since the association captures the set of source and destination attribute pairs that relate them, you can create the view link just by indicating which association it should be based on.

7.4.1 How to Create an Association-Based View Link

To create an association-based view link, you use the Create View Link wizard.

To create an association-based view link

  1. From the New Gallery in the Business Tier > ADF Business Components category, select the Create View Link wizard.

  2. In step 1 on the Name page, supply a package and a component name. Assume you want to create the view link in the devguide.model.queries.viewlinks package and call it RequestsAssignedToTechnician.

  3. In step 2 on the View Objects page, in the Select Source Attribute tree expand the source StaffList view object in the devguide.model.queries package. In the Select Destination Attribute tree expand the ServiceRequests view object. Notice that in addition to the view object attributes, for entity-based view objects relevant associations also appear in the list. As shown in Figure 7-12, select the same ServiceRequestsAssignedToUser association in both Source and Destination trees, then click Add to add the association to the table below. Click Next and Finish to complete creating the new view link.

    Figure 7-12 Selecting an Association Relating the Source View Object's Entity Usage to the Destination's

    Step 2 of the View Objects wizard
  4. Next, create another association-based view link between the ServiceRequests view object and a view object that displays the detail information about the service request's history entries. You already have the master ServiceRequests view object, but you need to first create a view object for the detail before you can link them. Using the shortcut you learned above, in the Application Navigator select the ServiceHistory entity object in the devguide.model.entities package and choose New Default View Object... from the context menu to create a view object named ServiceHistories in the devguide.model.queries based on this entity.

    Note:

    In the Create Default View Object you can use the combobox to select devguide.model.queries from the list of existing packages.
  5. Finally, repeat the steps used above to create an association-based view link between the ServiceRequests view object and the new ServiceHistories view object based on the association that relates their respective primary entity usages. Name the view link HistoryLinesForRequest in the devguide.model.queries.viewlinks package. As an additional shortcut, to avoid having to type in the package name, as shown in Figure 7-13 you can use the New View Link... on the context menu of the viewlinks package node in the Application Navigator.

    Figure 7-13 Shortcut for Creating a View Link in an Existing Package

    Image shows context menus in Application Navigator

7.4.2 What Happens When You Create an Association-Based View Link

When you create an association-based view link, JDeveloper creates the XML component definition file that represents its declarative settings and saves it in the directory that corresponds to the name of its package. In the example above, the view links were named RequestsAssignedToTechnician and HistoryLinesForRequest in the devguide.model.queries.viewlinks package, so the XML files created will be /RequestsAssignedToTechnician.xml and /HistoryLinesForRequest.xml in the ./devguide/model/queries/viewlinks directory under the project's source path. This XML file contains the declarative information about the association that relates the source and target view objects you've specified. In addition to saving the view link component definitions themselves, JDeveloper also updates the XML definition of the source view objects in the view link relationships to add information about the view link accessor attribute.

7.5 Testing Entity-Based View Objects Interactively

You test entity-based view objects interactively in the same way as read-only ones. Just add instances of the desired view objects to the data model of some application module, and then test that application module using the Business Components Browser.

7.5.1 Overview of Business Component Browser Functionality for an Updatable Data Model

You'll find the Business Components Browser tool invaluable in quickly testing and debugging your application modules. Figure 7-14 gives an overview of the operations that all of the Business Components Browser toolbar buttons perform.

Figure 7-14 Business Component Browser Functionality for Updatable Data Models

Image of object functionality for Updatable Data Models

7.5.2 Adding View Object Instances to the Data Model

Following the same steps as you learned in Section 5.10.4.3, "How to Enable Active Master/Detail Coordination in the Data Model", add the following view object instances to the data model of the SRService application module to end up with the hierarchy of master/detail view objects shown in Figure 7-15:

  • Select existing ServiceRequests view object instance in the Data Model tree first, then add a detail instance named ServiceHistories of the ServiceHistories view object that appears as a child of ServiceRequests in the Available list.

  • Select existing StaffList view object instance in the Data Model tree first, then add a detail instance named AssignedServiceRequests of the ServiceRequests view object that appears as a child of StaffList in the Available list.

  • Select the new AssignedServiceRequests view object instance in the Data Model tree first, then add a detail instance named AssignedServiceHistories of the ServiceHistories view object that appears as a child of ServiceRequests in the Available list.

Figure 7-15 Business Components Browser Showing Editable Results of an Entity-Based View Object

Image of Data Model page of Business Components browser

7.5.3 How to Test Entity-Based View Objects Interactively

Assuming that you've set up the SRService application module's data model as shown in Figure 7-15, to test it do the following:

To test entity-based view objects:

  1. Select the application module in the Application Navigator and choose Test... from the context menu.

  2. Click Connect on the Business Components Browser Connect dialog to use the default SRServiceLocal configuration for testing.

Note:

By default, an application module has only its default, local configuration, named AppModuleNameLocal. If you have created additional configurations for your application module and want to test it using one of those instead, just select the desired configuration from the Business Components Configuration dropdown list on the Connect dialog before clicking Connect.

7.5.4 What Happens When You Test Entity-Based View Objects Interactively

When you launch the Business Components Browser, JDeveloper starts the tester tool in a separate process and the Business Component Browser appears. As shown in Figure 7-16 the tree at the left of the display shows the hierarchy of the view object instances in the data model, and includes additional nodes between a master view object instance and a detail view object instance that represent the view link instance that performs the active master/detail coordination as the current row changes in the master.

Figure 7-16 SRService Data Model in the Business Components Tester

Image of data model in Busines Components tester

Double-clicking on the HistoryLinesForRequest2 view link instance in the tree executes the master objects — if it has not been executed so far in the testing session — and displays the master/detail tester panel shown in Figure 7-17. Additional context menu items on the view object node allow you to reexecute the query if needed, remove the tester panel, and perform other tasks. You saw a similar master/detail panel when you used the Business Components Browser in Section 5.5, "Testing View Objects Using the Business Components Browser". You can see and scroll through the query results. One important difference is a direct result of using an entity-based view object this time. Instead of seeing disabled UI controls showing read-only data, you now see editable fields and are free to experiment with creating, inserting, updating, validating, committing and rolling back.

Figure 7-17 Instances of Entity-Based View Objects are Fully Editable by the Tester

Image shows how objects are edited in in tester

Try experimenting with the multiple levels of master/detail hierarchies, opening multiple tester panels at the same time, and using the Display Results in a Window toolbar button to "pop" a tab out of the frame into a separate window to see multiple view object's data at the same time.

7.5.5 Simulating End-User Interaction with Your Application Module Data Model

Using the Business Components Browser, you can simulate an end user interacting with your application module data model before you have started to build any custom user interface of your own. Even after you have your UI pages constructed, you will come to appreciate using the Business Components Browser to assist in diagnosing problems when they arise. You can reproduce the issues in the Business Components Browser to discover if the issue lies in the view or controller layers of the application, or is instead a problem in the business service layer application module itself.

Using just the master/detail tester page shown in Figure 7-17, you can test several functional areas of your application.

7.5.5.1 Testing Master/Detail Coordination

Use the navigation buttons on the toolbar, you can see that the service history rows for the current service request are correctly co-ordinated.

7.5.5.2 Testing UI Control Hints

The prompts displayed in the testing panels help you see whether you have correctly defined a user-friendly Label Text control hint for each attribute. For example, the RequestDate attribute in the ServiceRequests view object instance has the prompt Requested On. Hovering your mouse over the edit field for the RequestDate field, you'll shortly see the Tooltip control hint text appear if you've defined it. The tooltip that appears says, "The date on which the service request was created", which is how you set up the hint on your entity object back in Section 6.5.1, "How to Add Attribute Control Hints". Since you didn't specifically define any new control hints for the ServiceRequests view object, this illustrates that the entity-based view object attributes inherit their control hints from those on the underlying entity object attribute.

7.5.5.3 Testing View Objects That Reference Entity Usages

By scrolling through the data — or using Specify View Criteria toolbar button to search — you can verify whether service requests that have not yet been assigned are correctly displaying. If you correctly altered the query's WHERE clause to use an outer join, these row will appear as expected.

By changing the AssignedTo attribute to a different technician's user ID — double-click on the StaffList view object instance in the Business Components Browser to browse for some valid staff user ID's — you can verify that the corresponding reference information is automatically updated to reflect the new technician.

By observing the values of the ProductName field and the two Email Address fields in some example rows of the ServiceRequests, you can see the corresponding user-friendly product name, and the email addresses of both the customer who created the request, and the technician to whom it is assigned. You can also notice a problem that both the customer's email address and the technician's email address are inheriting the Label Text hint from the User entity object of Email Address. It won't be clear to the end user which email address is which.

To remedy the situation where both the technician's email address and the customer's email address display with the same, inherited Email Address label, edit the ServiceRequests view object and define a Label Text control hint for both at the view object level. Set the Label Text hint to Technician Email Address for the TechnicianEmail attribute and Customer Email Address for the CustomerEmail attribute. Use the Business Components Browser to verify that these control hints defined at the view object level override the ones it would normally inherit from the underlying entity object.

7.5.5.4 Testing Business Domain Layer Validation

Try to change the Status attribute value of a Closed service request to have a value of Reopened. When you try to tab out of the field, you'll get an exception:

(oracle.jbo.AttrSetValException) The status must be Open, Pending, or Closed

Based on the other simple declarative validation rules defined in Section 6.7, "Using Declarative Validation Rules", you can try to update a ProblemDescription value to contain the word urgent surrounded by spaces, to receive the error:

(oracle.jbo.AttrSetValException)
Problem Description cannot contain the word urgent

Lastly, you can try to enter a ProdId value of 1234 to violate the range validation rule and see:

(oracle.jbo.AttrSetValException) Valid product codes are between 100 and 999

Click on the rollback button in the toolbar to revert data to the previous state.

7.5.5.5 Testing Alternate Language Message Bundles and Control Hints

By opening the Properties tab on the Connect dialog when you launch the Business Components Browser, you can override the default locale settings to change:

  • jbo.default.country = IT

  • jbo.default.language = it

With these properties set, you can see whether the Italian language translations of the ServiceRequest entity object control hints are correctly located. You'll notice Stato, Aperto Il, and Problema labels instead of Status, Requested On, and Problem (among others). You also will see that the format of RequestDate changes from a value like 03/12/2006 16:55 to 12/03/2006 16:55.

7.5.5.6 Testing Row Creation and Default Value Generation

Click on the Create Row button in the toolbar for the ServiceRequests view object instance to create a new blank row. Any fields that have a declarative default value will appear with that value in the blank row. The DBSequence-valued SvrId attribute appears read-only in the new row with its temporary negative number. After entering all the required fields — try 100 for the ProdId and 300 for the Requested By field — click on the commit button to commit the transaction. The actual, trigger-assigned primary key appears in the SvrId field after successful commit.

7.5.5.7 Testing New Detail Rows Have Correct Foreign Keys

If you try adding a new service history row to an existing service request, you'll notice that the view link automatically ensures the foreign key attribute value for SvrId in the new ServiceHistories row is set to the value of the current master service request row.

7.6 Adding Calculated and Transient Attributes to an Entity-Based View Object

In addition to having attributes that map to underlying entity objects, your view objects can include calculated attributes that don't map to any entity object attribute value. The two kinds of calculated attributes are known as:

This section explains how to add both kinds, first illustrating how to add a SQL-calculated LastCommaFirst attribute and then a transient-calculated attribute named FirstDotLast to the StaffList view object. Finally, you'll see that a view object can include an entity-mapped attribute which itself is a transient attribute at the entity object level just to ensure that all of the supported combinations are clear.

7.6.1 How to Add a SQL-Calculated Attribute

To add a SQL-calculated attribute to an entity-based view object:

  1. Open the Attributes page in the View Object Editor and click New.

  2. Enter a name for the attribute, such as LastCommaFirst.

  3. Set the Java Attribute Type to an appropriate value, like String.

  4. Check the Mapped to Column of SQL checkbox.

  5. Provide a SQL expression in the Expression field like LAST_NAME||', '||FIRST_NAME

  6. Consider changing the SQL column alias to match the name of the attribute

  7. Verify the database Query Column Type and adjust the length (or precision/scale) as appropriate.

  8. Click OK to create the attribute.

Figure 7-18 Adding a New SQL-Calculated Attribute

Image of New View Object Attribute dialog

7.6.2 What Happens When You Add a SQL-Calculated Attribute

When you add a SQL-calculated attribute and finish the View Object Editor, JDeveloper updates the XML component definition for the view object to reflect the new attribute. Whereas an entity-mapped attribute like LastName looks like this in the XML, inheriting most of it properties from the underlying entity attribute to which it is mapped:

<ViewAttribute
   Name="LastName"
   IsNotNull="true"
   EntityAttrName="LastName"
   EntityUsage="User1"
   AliasName="LAST_NAME" >
</ViewAttribute>

in contrast, a SQL-calculated attribute's ViewAttribute tag looks like the following. As expected, it has no EntityUsage or EntityAttrName property, and includes datatype information along with the SQL expression:

<ViewAttribute
   Name="LastCommaFirst"
   IsUpdatable="false"
   IsPersistent="false"
   Precision="62"
   Type="java.lang.String"
   ColumnType="VARCHAR2"
   AliasName="FULL_NAME"
   Expression="LAST_NAME||&#39;, &#39;||FIRST_NAME"
   SQLType="VARCHAR" >
</ViewAttribute>

Note:

The &#39; is the XML character reference for the apostrophe, referencing it by its numerical ASCII code of 39 (decimal). Other characters in literal text that require similar construction in XML are the less-than, greater-than, and ampersand characters.

7.6.3 How to Add a Transient Attribute

To add a transient attribute to an entity-based view object:

  1. Open the Attributes page in the View Object Editor and click New.

  2. Enter a name for the attribute, like FirstDotLast.

  3. Set the Java Attribute Type to String.

  4. Leave the Mapped to Column of SQL checkbox unchecked.

  5. Click OK to create the attribute.

Figure 7-19 Adding a New Transient Attribute

Image of New View Object Attribute dialog

7.6.3.1 Adding an Entity-Mapped Transient Attribute to a View Object

To add a transient entity object attribute to an entity-based view object, first ensure that you have an entity usage for the entity on the Entity Objects page of the View Object Editor. Then go to Attributes page and the desired attribute from the Available list into the Selected list. Using these steps, you can add the FullName calculated attribute from the User entity object to the StaffList view object.

If you use the Business Components Browser to test the SRService data model after adding these three attributes to the StaffList view object lists, you can see their effect as shown in Figure 7-20:

Figure 7-20 StaffList View Object with Three Kinds of Calculated Attributes

Image of StaffList view object in tester

7.6.4 What Happens When You Add a Transient Attribute

When you add a transient attribute and finish the View Object Editor, JDeveloper updates the XML component definition for the view object to reflect the new attribute. A transient attribute's ViewAttribute tag in the XML is similar to the SQL-calculated one, but lacks an Expression property.

7.6.5 Adding Java Code in the View Row Class to Perform Calculation

A transient attribute is a placeholder for a data value. If you change the Updatable property of the transient attribute to While New or Always, the end user can enter a value for the attribute. If you want the transient attribute to display a calculated value, then you'll typically leave the Updatable property set to Never and write custom Java code that calculates the value.

After adding a transient attribute to the view object, to make it a calculated transient attribute you need to:

  • Enable a custom view row class on the Java page of the View Object Editor, choosing to generate accessor methods

  • Write Java code inside the accessor method for the transient attribute to return the calculated value

For example, after enabling the generation of the StaffListRowImpl.java view row class, the Java code to return its calculated value would reside in the getLastCommaFirst() method like this:

// In StaffListRowImpl.java
public String getFirstDotLast() {
  // Commented out this original line since we're not storing the value
  // return (String) getAttributeInternal(FIRSTDOTLAST);
  return getFirstName().substring(0,1)+". "+getLastName();
}

Note:

In Section 26.8, "Implementing Automatic Attribute Recalculation", you'll learn a coding technique to cause calculated attributes at the entity row level to be re-calculated when one of the attribute values on which they depend is modified. You could adopt a very similar strategy at the view row level to cause automatic recalculation of calculated view object attributes, too.

7.6.6 What You May Need to Know About Transient Attributes

The view object includes the SQL expression for your SQL-calculated attribute in the SELECT list of its query at runtime. The database is the one that evaluates the expression and it returns the result as the value of that column in the query. The value gets reevaluated each time you execute the query.

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

7.8 Working Programmatically with Entity-Based View Objects

From the point of view of a client accessing your application module's data model, the API's to work with a read-only view object and an entity-based view object are identical. The key functional difference is that entity-based view objects allow the data in a view object to be fully updatable. The application module that contains the entity-based view objects defines the unit of work and manages the transaction. This section presents four simple test client programs that work with the SRService application module to illustrate:

7.8.1 Example of Iterating Master/Detail/Detail Hierarchy

Example 7-1 performs the following basic steps:

  1. Finds the StaffList view object instance

  2. Executes the query

  3. Iterate over the resulting StaffList rows

  4. Print the staff member's full name by getting the value of the calculated FullName attribute

  5. Get related row set of ServiceRequests using a view link accessor attribute

  6. Iterate over the ServiceRequests rows

  7. Print out some service request attribute values

  8. If the status is not Closed, then get the related row set of ServiceHistories using a view link accessor attribute

  9. Iterate over the ServiceHistories rows

  10. Print out some service request history attributes

Note:

Other than having one additional level of nesting, this example uses the same API's that you saw in the TestClient2 program that was iterating over master/detail read-only view objects in Section 5.10.4.2, "How to Access a Detail Collection Using the View Link Accessor".

Example 7-1 Iterating Master/Detail/Detail Hierarchy

package devguide.client;
import oracle.jbo.ApplicationModule;
import oracle.jbo.Row;
import oracle.jbo.RowSet;
import oracle.jbo.ViewObject;
import oracle.jbo.client.Configuration;
public class TestClient {
  public static void main(String[] args) {
    String        amDef = "devguide.model.SRService";
    String        config = "SRServiceLocal";
    ApplicationModule am =
      Configuration.createRootApplicationModule(amDef,config);
    // 1. Find the StaffList view object instance.
    ViewObject staffList = am.findViewObject("StaffList");
    // 2. Execute the query
    staffList.executeQuery();
    // 3. Iterate over the resulting rows
    while (staffList.hasNext()) {
      Row staffMember = staffList.next();
      // 4. Print the staff member's full name
      System.out.println("Staff Member: "+staffMember.getAttribute("FullName"));
      // 5. Get related rowset of ServiceRequests using view link accessor
      RowSet reqs = (RowSet)staffMember.getAttribute("ServiceRequests");
      // 6. Iterate over the ServiceRequests rows
      while (reqs.hasNext()) {
        Row svcreq = reqs.next();
        // 7. Print out some service request attribute values
        System.out.println(" ["+svcreq.getAttribute("Status")+"] "+
                               svcreq.getAttribute("SvrId")+": "+
                               svcreq.getAttribute("ProblemDescription"));
        if(!svcreq.getAttribute("Status").equals("Closed")) {
          // 8. Get related rowset of ServiceHistories
          RowSet hists = (RowSet)svcreq.getAttribute("ServiceHistories");
          // 9. Iterate over the ServiceHistories rows
          while (hists.hasNext()) {
            Row hist = hists.next();
            // 10. Print out some service request history attributes
            System.out.println("  "+hist.getAttribute("LineNo")+": "+
                               hist.getAttribute("Notes"));
          }          
        }
      }
    }
    Configuration.releaseRootApplicationModule(am,true);
  }  
}

Running the program produces the following output:

Staff Member: David Austin
 [Open] 104: Spin cycle not draining
  1: Researching issue
Staff Member: Bruce Ernst
 [Closed] 101: Agitator does not work
 [Pending] 102: Washing Machine does not turn on
  1: Called customer to make sure washer was plugged in...
  2: We should modify the setup instructions to include...
 [Open] 108: Freezer full of frost
  1: Researching issue
Staff Member: Alexander Hunold
 [Closed] 100: I have noticed that every time I do a...
 [Closed] 105: Air in dryer not hot
   :

7.8.2 Example of Finding a Row and Updating a Foreign Key Value

Example 7-2 performs the following basic steps:

  1. Finds the ServiceRequests view object instance

  2. Constructs a Key object to look up the row for service request number 101

  3. Uses findByKey() to find the row

  4. Prints some service request attribute values

  5. Tries to assign the illegal value Reopened to the Status attribute

    Since view object rows cooperate with entity objects, the validation rule on the Status attribute throws an exception, preventing this illegal change.

  6. Sets the Status to a legal value of Open

  7. Prints the value of the Status attribute to show it was updated successfully

  8. Prints the current value of the assigned technician's email

  9. Reassigns the service request to technician number 303 (Alexander Hunold) by setting the AssignedTo attribute

  10. Shows the value of the reference information (TechnicianEmail) reflecting a new technician

  11. Cancels the transaction by issuing a rollback

Example 7-2 Finding and Updating a Foreign Key Value

package devguide.client;
import oracle.jbo.ApplicationModule;
import oracle.jbo.JboException;
import oracle.jbo.Key;
import oracle.jbo.Row;
import oracle.jbo.ViewObject;
import oracle.jbo.client.Configuration;
public class TestFindAndUpdate {
  public static void main(String[] args) {
    String        amDef = "devguide.model.SRService";
    String        config = "SRServiceLocal";
    ApplicationModule am =
      Configuration.createRootApplicationModule(amDef,config);
    // 1. Find the ServiceRequests view object instance
    ViewObject vo = am.findViewObject("ServiceRequests");
    // 2. Construct a new Key to find ServiceRequest# 101
    Key svcReqKey = new Key(new Object[]{101});
    // 3. Find the row matching this key 
    Row[] reqsFound = vo.findByKey(svcReqKey,1);
    if (reqsFound != null && reqsFound.length > 0) {
      Row svcReq = reqsFound[0];
      // 4. Print some service request information
      String curStatus = (String)svcReq.getAttribute("Status");
      System.out.println("Current status is: "+curStatus);
      try {
        // 5. Try setting the status to an illegal value
        svcReq.setAttribute("Status","Reopened");
      }
      catch (JboException ex) {
        System.out.println("ERROR: "+ex.getMessage());
      }
      // 6. Set the status to a legal value
      svcReq.setAttribute("Status","Open");
      // 7. Show the value of the status was updated successfully
      System.out.println("Current status is: "+svcReq.getAttribute("Status"));
      // 8. Show the current value of the assigned technician
      System.out.println("Assigned: "+svcReq.getAttribute("TechnicianEmail"));
      // 9. Reassign the service request to technician # 303
      svcReq.setAttribute("AssignedTo",303); // Alexander Hunold (technician)
      // 10. Show the value of the reference info reflects new technician
      System.out.println("Assigned: "+svcReq.getAttribute("TechnicianEmail"));
      // 11. Rollback the transaction
      am.getTransaction().rollback();
      System.out.println("Transaction cancelled");      
    }
    Configuration.releaseRootApplicationModule(am,true);
  }  
}

Running this example produces the following output:

Current status is: Closed
ERROR: The status must be Open, Pending, or Closed
Current status is: Open
Assigned: bernst
Assigned: ahunold
Transaction cancelled

7.8.3 Example of Creating a New Service Request

Example 7-3 performs the following basic steps:

  1. Find the ServiceRequests view object instance

  2. Creates a new row and inserts it into the row set

  3. Shows the effect of entity object related defaulting for Status attribute

  4. Sets values of some required attributes in the new row

  5. Commits the transaction

  6. Retrieves and displays the trigger-assigned service request ID

Example 7-3 Creating a New Service Request

package devguide.client;
import java.sql.Timestamp;
import oracle.jbo.ApplicationModule;
import oracle.jbo.Row;
import oracle.jbo.ViewObject;
import oracle.jbo.client.Configuration;
import oracle.jbo.domain.DBSequence;
import oracle.jbo.domain.Date;
public class TestCreatingServiceRequest {
  public static void main(String[] args) throws Throwable {
    String amDef = "devguide.model.SRService";
    String config = "SRServiceLocal";
    ApplicationModule am =
      Configuration.createRootApplicationModule(amDef, config);
    // 1. Find the ServiceRequests view object instance.
    ViewObject svcReqs = am.findViewObject("ServiceRequests");
    // 2. Create a new row and insert it into the row set
    Row newSvcReq = svcReqs.createRow();
    svcReqs.insertRow(newSvcReq);
    // 3. Show effect of entity object defaulting for Status attribute
    System.out.println("Status defaults to: "+newSvcReq.getAttribute("Status"));
    // 4. Set values for some of the required attributes
    newSvcReq.setAttribute("CreatedBy",308); // Nancy Greenberg (user) 
    Date now = new Date(new Timestamp(System.currentTimeMillis()));
    newSvcReq.setAttribute("RequestDate",now);
    newSvcReq.setAttribute("ProdId",119); // Ice Maker
    newSvcReq.setAttribute("ProblemDescription","Cubes melt immediately");
    // 5. Commit the transaction
    am.getTransaction().commit();
    // 6. Retrieve and display the trigger-assigned service request id
    DBSequence id = (DBSequence)newSvcReq.getAttribute("SvrId");
    System.out.println("Thanks, reference number is "+id.getSequenceNumber());
    Configuration.releaseRootApplicationModule(am, true);
  }
}

Running this example produces the following results:

Status defaults to: Open
Thanks, reference number is 200

7.8.4 Example of Retrieving the Row Key Identifying a Row

Example 7-4 performs the following basic steps:

  1. Finds the ServiceRequests view object

  2. Constructs a key to find service request number 101

  3. Finds the ServiceRequests row with this key

  4. Displays the key of the ServiceRequests row

  5. Accesses the row set of ServiceHistories using the view link accessor attribute

  6. Iterates overs the ServiceHistories row

  7. Displays the key of each ServiceHistories row

Example 7-4 Retrieving the Row Key Identifying a Row

package devguide.client;
import oracle.jbo.ApplicationModule;
import oracle.jbo.Key;
import oracle.jbo.Row;
import oracle.jbo.RowSet;
import oracle.jbo.ViewObject;
import oracle.jbo.client.Configuration;
public class TestFindAndShowKeys {
  public static void main(String[] args) {
    String amDef = "devguide.model.SRService";
    String config = "SRServiceLocal";
    ApplicationModule am =
      Configuration.createRootApplicationModule(amDef, config);
    // 1. Find the ServiceRequests view object
    ViewObject vo = am.findViewObject("ServiceRequests");
    // 2. Construct a key to find service request number 101
    Key svcReqKey = new Key(new Object[] { 101 });
    // 3. Find the ServiceRequests row with this key
    Row[] reqsFound = vo.findByKey(svcReqKey, 1);
    if (reqsFound != null && reqsFound.length > 0) {
      Row svcReq = reqsFound[0];
      // 4. Display the key of the ServiceRequests row
      showKeyFor(svcReq);
      // 5. Access row set of ServiceHistories using view link accessor
      RowSet histories = (RowSet)svcReq.getAttribute("ServiceHistories");
      // 6. Iterate over the ServiceHistories row
      while (histories.hasNext()) {
        Row historyRow = histories.next();
        // 7. Display the key of the current ServiceHistories row
        showKeyFor(historyRow);
      }
    }
    Configuration.releaseRootApplicationModule(am, true);
  }
  private static void showKeyFor(Row r) {
    // get the key for the row passed in
    Key k = r.getKey();
    // format the key as "(val1,val2)"
    String keyAttrs=formatKeyAttributeNamesAndValues(k);
    // get the serialized string format of the key, too
    String keyStringFmt = r.getKey().toStringFormat(false);
    System.out.println("Key "+keyAttrs+" has string format "+keyStringFmt);
  }
  // Build up "(val1,val2)" string for key attributes
  private static String formatKeyAttributeNamesAndValues(Key k) {
    StringBuffer sb = new StringBuffer("(");
    int attrsInKey = k.getAttributeCount();
    for (int i = 0; i < attrsInKey;i++) {
      if (i > 0 ) sb.append(",");
      sb.append(k.getAttributeValues()[i]);
    }
    sb.append(")");
    return sb.toString();
  }
}

Running the example produces the following results. Notice that the serialized string format of a key is a hexadecimal number that includes information in a single string that represents all the attributes in a key.

Key (101) has string format 000100000003313031
Key (101,1) has string format 000200000003C2020200000002C102
Key (101,2) has string format 000200000003C2020200000002C103

7.9 Summary of Difference Between Entity-Based View Objects and Read-Only View Objects

You now know that view objects can either be related to underlying entity objects or not. This section helps summarize the difference between the runtime behavior of these two fundamental kinds of view objects.

7.9.1 Runtime Features Unique to Entity-Based View Objects

When a view object has one or more underlying entity usages you can create new rows, and modify or remove queried rows. The entity-based view object coordinates with underlying entity objects to enforce business rules and to permanently save the changes. In addition, you've seen that entity-based view objects:

  • Immediately reflect pending changes made to relevant entity object attributes made through other view objects in the same transaction

  • Initialize attribute values in newly created rows to the values from the underlying entity object attributes

  • Reflect updated reference information when foreign key attribute values are changed

7.9.2 View Objects with No Entity Usage Are Read-Only

View objects with no entity usage are read-only, do not pick up entity-derived default values, do not reflect pending changes, and do not reflect updated reference information. You need to decide what kind of functionality your application requires and design the view object accordingly. Typically view objects used for SQL-based validation purposes, and for displaying the list of valid selections in a dropdown list, can be read-only. There is a small amount of runtime overhead associated with the coordination between view object rows and entity object rows, so if you don't need any of the functionality offered by an entity-mapped view object, you can slightly increase performance by using a read-only view object with no related entity objects.

7.9.3 What You May Need to Know About Enabling View Object Key Management for Read-Only View Objects

An entity-based view object delegates the task of finding rows by key to its underlying entity row parts. When you use the findByKey() method to find a view row by key, the view row turns around and uses the entity definition's findByPrimaryKey() to find each entity row contributing attributes to the view row key.

This scheme is not possible for a read-only view object since it has no underlying entity row to which to delegate the job. Since you might use read-only view objects to quickly iterate over query results without needing any of the additional features provided by the entity-based view object, a read-only view object does not assume you want to incur the slight additional overhead of managing rows by key at the level of the view object's row set.

In order to successfully be able to use the findByKey() method on a read-only view object, you need to perform two additional steps:

  1. Ensure that at least one attribute in the view object has the Key Attribute property set

  2. Enable a custom Java class for the view object, and override its create() method to call setManageRowsByKey(true)after calling super.create() like this:

    // In custom Java class for read-only view object
    public void create() {
      super.create();
      setManageRowsByKey(true);
    }
    

Note:

In an application using the ADF Model layer for data binding to a read-only view object, the successful operation of the ADF Faces table — or other controls that allow the end user to set the current row by clicking in the page — require this additional step. This also applies to the successful use of built-in binding layer actions like setCurrentRowWithKey or setCurrentRowWithKeyValue on a read-only view object. These all boil down to calling findByKey() under the covers.

Section 25.3.2, "Implementing Generic Functionality Using Runtime Metadata" describes a generic technique you can use to avoid having to remember to do this on all of your read-only view objects on which you want findByKey() to work as expected.