Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g Release 3 (10.1.3.0) Part Number B25947-02 |
|
|
View PDF |
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:
Section 7.3, "Including Reference Entities in Join View Objects"
Section 7.5, "Testing Entity-Based View Objects Interactively"
Section 7.6, "Adding Calculated and Transient Attributes to an Entity-Based View Object"
Section 7.7, "Understanding How View Objects and Entity Objects Cooperate at Runtime"
Section 7.8, "Working Programmatically with Entity-Based View Objects"
Section 7.9, "Summary of Difference Between Entity-Based View Objects and Read-Only 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:
You define an updatable view object by referencing attributes from one or more entity objects.
You can use multiple, associated entity objects to simplify working with reference information.
You can define view links based on underlying entity associations.
You use your entity-based view objects in the context of an application module that provides the transaction.
At runtime, the view row delegates the storage and validation of its attributes to underlying entity objects.
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.
Note:
The examples in this chapter use the same basic SRDemo application business domain layer ofServiceRequest
, 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.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.
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.
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.
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.
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.
Click Finish at this point to create the view 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:
Select the desired entity object in the Application Navigator.
Choose New Default View Object... from the context menu.
Provide a package and component name for the new view object in the Create Default View Object dialog, as shown in Figure 7-6.
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
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 classStaffListImpl.java
and/or a custom view row class StaffListRowImpl.java
class.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.
Each view object attribute inherits the properties of the corresponding entity object attribute.
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.
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:
CreatedBy
, representing the user who created the request
AssignedTo
, representing the user to whom the request is assigned
ProdId
, representing the product to which the request pertains
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.
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.
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.
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.
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)
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)
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
.
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
Click OK to save your changes to the 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.
{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.
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.
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)
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.
To create an association-based view link, you use the Create View Link wizard.
To create an association-based view link
From the New Gallery in the Business Tier > ADF Business Components category, select the Create View Link wizard.
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
.
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.
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 selectdevguide.model.queries
from the list of existing packages.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.
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.
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.
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.
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.
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:
Select the application module in the Application Navigator and choose Test... from the context menu.
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, namedAppModuleName
Local
. 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.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.
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.
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.
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.
Use the navigation buttons on the toolbar, you can see that the service history rows for the current service request are correctly co-ordinated.
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.
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.
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.
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
.
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.
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.
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:
SQL-calculated attributes, when their value is retrieved as an expression in the SQL query's SELECT list
Transient attributes, when their value is not retrieved as part of the query
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.
To add a SQL-calculated attribute to an entity-based view object:
Open the Attributes page in the View Object Editor and click New.
Enter a name for the attribute, such as LastCommaFirst
.
Set the Java Attribute Type to an appropriate value, like String
.
Check the Mapped to Column of SQL checkbox.
Provide a SQL expression in the Expression field like LAST_NAME||', '||FIRST_NAME
Consider changing the SQL column alias to match the name of the attribute
Verify the database Query Column Type and adjust the length (or precision/scale) as appropriate.
Click OK to create the 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||', '||FIRST_NAME" SQLType="VARCHAR" > </ViewAttribute>
Note:
The'
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.To add a transient attribute to an entity-based view object:
Open the Attributes page in the View Object Editor and click New.
Enter a name for the attribute, like FirstDotLast
.
Set the Java Attribute Type to String
.
Leave the Mapped to Column of SQL checkbox unchecked.
Click OK to create the attribute.
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:
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.
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.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.
On their own, view objects and entity objects simplify two important jobs that every enterprise application developer needs to do:
Work with SQL query results
Modify and validate rows in database tables
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:
You decide what business logic should be enforced in your business domain layer
You decide what queries describe the data you need to put on the screen
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.
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.
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 totrue
. 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.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
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.
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))
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.
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.
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:
The client attempts to set the Status
attribute to the value Closed
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
.
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.
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.
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.
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:
Thejbo.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.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:
The client attempts to set the AssignedTo
attribute to the value 300
.
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
.
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.
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.
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.
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.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.
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 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
.
Suppose the user is happy with her changes, and commits the transaction. As shown in Figure 7-28, there are two basic steps:
The Transaction
object validates any invalid entity rows in its pending changes list.
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.
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
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:
Iterating master/detail/detail hierarchy
Finding a row and updating a foreign key value
Creating a new service request
Retrieving the row Key identifying a row
Example 7-1 performs the following basic steps:
Finds the StaffList
view object instance
Executes the query
Iterate over the resulting StaffList
rows
Print the staff member's full name by getting the value of the calculated FullName
attribute
Get related row set of ServiceRequests
using a view link accessor attribute
Iterate over the ServiceRequests
rows
Print out some service request attribute values
If the status is not Closed
, then get the related row set of ServiceHistories
using a view link accessor attribute
Iterate over the ServiceHistories
rows
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 theTestClient2
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 :
Example 7-2 performs the following basic steps:
Finds the ServiceRequests
view object instance
Constructs a Key
object to look up the row for service request number 101
Uses findByKey()
to find the row
Prints some service request attribute values
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.
Sets the Status
to a legal value of Open
Prints the value of the Status
attribute to show it was updated successfully
Prints the current value of the assigned technician's email
Reassigns the service request to technician number 303
(Alexander Hunold) by setting the AssignedTo
attribute
Shows the value of the reference information (TechnicianEmail
) reflecting a new technician
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
Example 7-3 performs the following basic steps:
Find the ServiceRequests
view object instance
Creates a new row and inserts it into the row set
Shows the effect of entity object related defaulting for Status
attribute
Sets values of some required attributes in the new row
Commits the transaction
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
Example 7-4 performs the following basic steps:
Finds the ServiceRequests
view object
Constructs a key to find service request number 101
Finds the ServiceRequests
row with this key
Displays the key of the ServiceRequests
row
Accesses the row set of ServiceHistories
using the view link accessor attribute
Iterates overs the ServiceHistories
row
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
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.
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
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.
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:
Ensure that at least one attribute in the view object has the Key Attribute property set
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 likesetCurrentRowWithKey
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.