This chapter describes how to use entity objects to create a reusable layer of business domain objects for use in your Java EE applications.
This chapter includes the following sections:
Section 4.4, "Creating an Entity Diagram for Your Business Layer"
Section 4.6, "Defining Attribute Control Hints for Entity Objects"
Section 4.11, "Adding Transient and Calculated Attributes to an Entity Object"
Section 4.13, "Generating Custom Java Classes for an Entity Object"
Section 4.14, "Working Programmatically with Entity Objects and Associations"
Section 4.15, "Creating Custom, Validated Data Types Using Domains"
Section 4.17, "Basing an Entity Object on a PL/SQL Package API"
Section 4.18, "Basing an Entity Object on a Join View or Remote DBLink"
Section 4.19, "Using Inheritance in Your Business Domain Layer"
An entity object is the ADF Business Components component that represents a row in the specified data source and simplifies modifying its associated attributes. Importantly, it allows you to encapsulate domain business logic to ensure that your business policies and rules are consistently validated.
Entity objects support numerous declarative business logic features to enforce the validity of your data. You will typically complement declarative validation with additional custom application logic and business rules to cleanly encapsulate a maximum amount of domain business logic into each entity object. Your associated set of entity objects forms a reusable business domain layer that you can exploit in multiple applications.
The key concepts of entity objects (as illustrated in Figure 4-1) are the following:
You define an entity object by specifying the database table whose rows it will represent.
You can create associations to reflect relationships between entity objects.
At runtime, entity rows are managed by a related entity definition object.
Each entity row is identified by a related row key.
You retrieve and modify entity rows in the context of an application module that provides the database transaction.
You may find it helpful to understand other ADF features before you start working with entity objects. Following are links to other functionality that may be of interest.
For information about using declarative validation in entity objects, see Chapter 7, "Defining Validation and Business Rules Declaratively."
For API documentation related to the oracle.jbo
package, see the following Javadoc reference document:
If you already have a database schema to work from, the simplest way to create entity objects and associations is to reverse-engineer them from existing tables. When needed, you can also create an entity object from scratch, and then generate a table for it later.
To create one or more entity objects, use the Business Components from Tables wizard, which is available from the New Gallery.
It may be helpful to have an understanding of the options you have for creating entity objects. For more information, see Section 4.2, "Creating Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create one or more entity objects and associations from existing tables:
In the Application Navigator, right-click the project in which you want to create the entity objects and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Business Components from Tables, and click OK.
If this is the first component you're creating in the project, the Initialize Business Components Project dialog appears to allow you to select a database connection.
In the Initialize Business Components Project dialog, select the database connection or choose New to create a connection. Click OK.
On the Entity Objects page, do the following to create the entity objects:
Enter the package name in which all of the entity objects will be created.
Select the tables from the Available list for which you want to create entity objects.
If the Auto-Query checkbox is selected, then the list of available tables appears immediately. In the Name Filter field, you can optionally enter a full or partial table name to filter the available tables list in real time. As an alternative to the auto-query feature, click the Query button to retrieve the list based on an optional table name filter. When no name filter is entered, JDeveloper retrieves all table objects for the chosen schema.
Click Filter Types if you want to see only a subset of the database objects available. You can filter out tables, views, or synonyms.
Once you have selected a table from the Available list, the proposed entity object name for that table appears in the Selected list with the related table name in parenthesis.
Select an entity object name in the Selected list and use the Entity Name field to change the default entity object name.
Best Practice:
Because each entity object instance represents a single row in a particular table, name the entity objects with a singular noun (like Address, Order, and Person), instead of their plural counterparts. Figure 4-2 shows what the wizard page looks like after selecting theADDRESSES
table in the FOD
schema, setting a package name of oracle.fodemo.storefront.entities
, and renaming the entity object in the singular.When you are satisfied with the selected table objects and their corresponding entity object names, click Finish.
The Application Navigator displays the entity objects in the package you specified.
Best Practice:
After you create associations, move all of your associations to a separate package so that you can view and manage them separately from the entity objects. In Figure 4-3, the associations have been moved to a subpackage (associations
) and do not appear in the entities
package in the Application Navigator. For more information, see Section 4.3.4, "How to Rename and Move Associations to a Different Package."To create a single entity object, you can use the Create Entity Object wizard, which is available in the New Gallery.
Note:
Associations are not generated when you use the Create Entity Object wizard. However, the Business Components from Tables wizard does generate associations. If you use the Create Entity Object wizard to create entity objects, you will need to create the corresponding associations manually.It may be helpful to have an understanding of the options you have for creating entity objects. For more information, see Section 4.2, "Creating Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create a single entity object:
In the Application Navigator, right-click the project in which you want to create the entity object and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Entity Object, and click OK.
If this is the first component you're creating in the project, the Initialize Business Components Project dialog appears to allow you to select a database connection.
In the Initialize Business Components Project dialog, select the database connection or choose New to create a connection. Click OK.
On the Name page, do the following to create the entity object:
Enter a name for the entity object.
Enter the package name in which the entity object will be created.
Click Browse (next to the Schema Object field) to select the table for which you want to create the entity object.
Or, if you plan to create the table later, you can enter a name of a table that does not exist.
If you manually entered a table name in the Schema Object field, you will need to define each attribute on the Attributes page of the wizard. Click Next.
You can create the table manually or generate it, as described in Section 4.2.6, "How to Create Database Tables from Entity Objects."
When you are satisfied with the table object and its corresponding entity object name, click Finish.
When you create an entity object from an existing table, first JDeveloper interrogates the data dictionary to infer the following information:
The Java-friendly entity attribute names from the names of the table's columns (for example, USER_ID
-> UserId
)
The SQL and Java data types of each attribute based on those of the underlying column
The length and precision of each attribute
The primary and unique key attributes
The mandatory flag on attributes, based on NOT NULL
constraints
The relationships between the new entity object and other entities based on foreign key constraints
Note:
Since an entity object represents a database row, it seems natural to call it an entity row. Alternatively, since at runtime the entity row is an instance of a Java object that encapsulates business logic for that database row, the more object-oriented term entity instance is also appropriate. Therefore, these two terms are interchangeable.JDeveloper then 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. For example, when an entity named Order
appears in the genericbcmodel.entities
package, JDeveloper will create the XML file genericbcmodel/entities/Order.xml
under the project's source path. This XML file contains the name of the table, the names and data types of each entity attribute, and the column name for each attribute.
You can inspect the XML description for the entity object by opening the object in the overview editor and clicking the Source tab.
Note:
If your IDE-level Business Components Java generation preferences so indicate, the wizard may also create an optional custom entity object class (for example,OrderImpl.java
).In addition to the entity objects, the Business Components from Tables wizard also generates named association components that capture information about the relationships between entity objects. For example, the database diagram in Figure 4-4 shows that JDeveloper derives default association names like OrderItemsProductsFkAssoc
by converting the foreign key constraint names to a Java-friendly name and adding the Assoc
suffix. For each association created, JDeveloper creates an appropriate XML component definition file and saves it in the directory that corresponds to the name of its package.
Note:
Associations are generated when you use the Business Components from Tables wizard. However, the Create Entity Object wizard does not generate associations. If you use the Create Entity Object wizard to create entity objects, you will need to create the corresponding associations manually.By default the associations reverse-engineered from foreign keys are created in the same package as the entities. For example, for the association OrderItemsProductsFkAssoc
with entities in the fodemo.storefront.entities
package, JDeveloper creates the association XML file named ./fodemo/storefront/entities/OrderItemsProductsFkAssoc.xml
.
If a table has no primary key constraint, then JDeveloper cannot infer the primary key for the entity object. Since every entity object must have at least one attribute marked as a primary key, the wizard will create an attribute named RowID
and use the database ROWID
value as the primary key for the entity. If appropriate, you can edit the entity object later to mark a different attribute as a primary key and remove the RowID
attribute. When you use the Create Entity Object wizard and you have not set any other attribute as primary key, you will be prompted to use RowID
as the primary key.
When you create an entity object using the Business Components from Tables wizard or the Create Entity Object wizard, the object can represent an underlying table, synonym, or view. The framework can infer the primary key and related associations for a table or synonym by inspecting database primary and foreign key constraints in the data dictionary.
However, when your selected schema object is a database view, then neither the primary key nor associations can be inferred since database views do not have database constraints. In this case, if you use the Business Components from Tables wizard, the primary key defaults to RowID
. If you use the Create Entity Object wizard, you'll need to specify the primary key manually by marking at least one of its attributes as a primary key. For more information, see Section 4.2.3.2, "What Happens When a Table Has No Primary Key."
When your selected schema object is a synonym, there are two possible outcomes. If the synonym is a synonym for a table, then the wizard and editor behave as if you had specified a table. If instead the synonym refers to a database view, then they behave as if you had specified a view.
After you've created a new entity object or association, you can edit any of its settings in the overview editor. To launch the editor, choose Open from the context menu for the entity object or association in the Application Navigator or double-click the object. By clicking the different tabs of the editor, you can adjust the settings that define the object and govern its runtime behavior.
To create database tables based on entity objects, right-click the package in the Application Navigator that contains the entity objects and choose Create Database Objects from the context menu. A dialog appears to let you select the entities whose tables you'd like to create. This tool can be used to generate a table for an entity object you created from scratch, or to drop and re-create an existing table.
Caution:
This feature does not generate a DDL script to run later. It performs its operations directly against the database and will drop existing tables. A dialog appears to confirm that you want to do this before proceeding. For entities based on existing tables, use with caution.In the overview editor for an association, the Use Database Key Constraints checkbox on the Association Properties page controls whether the related foreign key constraint will be generated when creating the tables for entity objects. Selecting this option does not have any runtime implications.
Inevitably you (or your DBA) might alter a table for which you've already created an entity object. Your existing entity will not be disturbed by the presence of additional attributes in its underlying table; however, if you want to access the new column in the table in your Java EE application, you'll need to synchronize the entity object with the database table.
For example, suppose you had done the following at the SQL*Plus command prompt to add a new SECURITY_QUESTION
column to the PERSONS
table:
ALTER TABLE PERSONS ADD (security_question VARCHAR2(60));
Then you can use the synchronization feature to add the new column as an attribute on the entity object.
It may be helpful to have an understanding of the options you have for creating entity objects. For more information, see Section 4.2, "Creating Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To synchronize an entity with changes to its database table:
In the Application Navigator, right-click the desired entity object and choose Synchronize with Database.
The Synchronize with Database dialog shows the list of the actions that can be taken to synchronize the business logic tier with the database.
Select the action you want to take:
Select one or more actions from the list, and click Synchronize to synchronize the selected items.
Click Synchronize All to perform all actions in the list.
Click Write to File to save the action list to a text file. This feature helps you keep track of the changes you make.
When finished, click OK to close the dialog.
The synchronize feature does not handle dropped columns. When a column is dropped from the underlying database after an entity object has been created, you can delete the corresponding attribute from the entity object. If the attribute is used in other parts of your application, you must remove those usages as well.
It may be helpful to have an understanding of the options you have for creating entity objects. For more information, see Section 4.2, "Creating Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To remove an entity attribute:
In the Application Navigator, double-click the entity.
In the overview editor, click the Attributes navigation tab.
On the Attributes page, right-click the attribute, and choose Delete Safely.
If there are other usages, the Delete Attributes dialog displays the message "Usages were found."
If usages were found, click View Usages.
The Log window shows all usages of the attribute.
Work through the list in the Log window to delete all usages of the entity attribute.
The synchronize feature does not handle changed data types. For a data type change in the underlying table (for example, precision increased), you must locate all usages of the attribute and manually make changes, as necessary.
It may be helpful to have an understanding of the options you have for creating entity objects. For more information, see Section 4.2, "Creating Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To locate all usages of an entity attribute:
In the Application Navigator, double-click the entity.
In the overview editor, click the Attributes navigation tab.
On the Attributes page, right-click the attribute and choose Find Usages.
If there are other usages, they are displayed in the Log window.
Effective dated tables are used to provide a view into the data set pertaining to a specific point in time. Effective dated tables are widely used in applications like HRMS and Payroll to answer queries like:
What was the tax rate for an employee on August 31st, 2005?
What are the employee's benefits as of October 2004?
In either case, the employee's data may have changed to a different value since then.
The primary difference between the effective dated entity type and the dated entity type is that the dated entity does not cause row splits during update and delete.
When you create an effective dated entity object, you identify the entity as effective dated and specify the attributes of the entity that represent the start and end dates. The start date and end date attributes must be of the Date type.
Additionally, you can specify an attribute that represents the sequence for the effective dated entity and an attribute that represents a flag for the sequence. These attributes allow for tracking of multiple changes in a single day.
It may be helpful to have an understanding of the options you have for creating entity objects. For more information, see Section 4.2, "Creating Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create an effective dated entity object
In the Application Navigator, double-click the entity on which you want enable effective dating.
In the Property Inspector, expand the Type category.
If necessary, choose Property Inspector from the View menu to display the Property Inspector.
If the Type category is not displayed in the Property Inspector, click the General tab in the overview editor to set the proper focus.
From the context menu for the Effective Date Type property, choose Edit.
To display the context menu, click the down arrow next to the property field.
In the Edit Property dialog, specify the following settings:
For Effective Date Type, select EffectiveDated.
For Start Date Attribute, select the attribute that corresponds to the start date.
For End Date Attribute, select the attribute that corresponds to the end date.
You can optionally specify attributes that allow for tracking of multiple changes in a single day.
For Effective Date Sequence, select the attribute that stores the sequence of changes.
For Effective Date Sequence Flag, select the attribute that stores a flag indicating the most recent change in the sequence.
Without specifying the Effective Date Sequence and Effective Date Sequence Flag attributes, the default granularity of effective dating is one day. For this reason, multiple changes in a single day are not allowed. An attempt to update the entity a second time in a single day will result in an exception being thrown. After these two attributes are specified, the framework inserts and updates their values as necessary to track multiple changes in a single day.
Click OK.
Note:
You can also identify the start and end date attributes using the Property Inspector for the appropriate attributes. To do so, select the appropriate attribute in the overview editor and set the IsEffectiveStartDate or IsEffectiveEndDate property to true in the Property Inspector.When you create an effective dated entity object, JDeveloper creates a transient attribute called SysEffectiveDate
to store the effective date for the row. Typically the Insert, Update, and Delete operations modify the transient attribute while the ADF Business Components framework decides the appropriate values for the effective start date and the effective end date.
Example 4-1 show some sample XML entries that are generated when you create an effective dated entity. For more information about working with effective dated objects, see Section 5.4, "Limiting View Object Rows Using Effective Date Ranges."
Example 4-1 XML Entries for Effective Dated Entities
// In the effective dated entity <Entity ... EffectiveDateType="EffectiveDated"> // In the attribute identified as the start date <Attribute ... IsEffectiveStartDate="true"> // In the attribute identified as the end date <Attribute ... IsEffectiveEndDate="true"> // The SysEffectiveDate transient attribute <Attribute Name="SysEffectiveDate" IsQueriable="false" IsPersistent="false" ColumnName="$none$" Type="oracle.jbo.domain.Date" ColumnType="$none$" SQLType="DATE"/>
The Business Components from Tables wizard makes it easy to quickly generate many business components at the same time. In practice, this does not mean that you should use it to immediately create entity objects for every table in your database schema just because it is possible to do so. If your application requires all of the tables, then that strategy might be appropriate. But because you can use the wizard whenever needed, you should create the entity objects for the tables that you know will be involved in the application.
Section 9.4, "Defining Nested Application Modules," describes a use case-driven design approach for your business services that can assist you in understanding which entity objects are required to support your application's business logic needs. You can always add more entity objects later as necessary.
If your database tables have no foreign key constraints defined, JDeveloper won't be able to infer the associations between the entity objects that you create. Since several ADF Business Components runtime features depend on the presence of entity associations, create them manually if the foreign key constraints don't exist.
To create an association, use the Create New Association wizard, which is available in the New Gallery.
It may be helpful to have an understanding of why you create associations. For more information, see Section 4.3, "Creating and Configuring Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
In the Application Navigator, right-click the project in which you want to create the association and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Association, and click OK.
On the Name page, do the following to create the association:
Enter the package name in which the association will be created.
Enter the name of the association component.
Click Next.
On the Entity Objects page, select the source and destination entity attributes:
Select a source attribute from one of the entity objects that is involved in the association to act as the master.
Select a corresponding destination attribute from the other entity object involved in the association.
For example, Figure 4-5 shows the selected OrderId
attribute from the OrderEO
entity object as the source entity attribute. Because the OrderItemEO
rows contain an order ID that relates them to a specific OrderEO
row, you would select this OrderId
foreign key attribute in the OrderItemEO
entity object as the destination attribute.
Click Add to add the matching attribute pair to the table of source and destination attribute pairs below.
By default, the Bound checkbox is selected for both the source and destination attribute. This checkbox allows you to specify whether or not the value will be bound into the association SQL statement that is created internally when navigating from source entity to target entity or from target entity to source entity (depending on which side you select).
Typically, you would deselect the checkbox for an attribute in the relationship that is a transient entity attribute whose value is a constant and therefore should not participate in the association SQL statement to retrieve the entity.
If the association requires multiple attribute pairs to define it, you can repeat the preceding steps to add additional source/target attribute pairs.
Finally, ensure that the Cardinality dropdown correctly reflects the cardinality of the association. The default is a one-to-many relationship. Click Next.
For example, since the relationship between a OrderEO
row and its related OrderItemEO
rows is one-to-many, you can leave the default setting.
On the Association SQL page, you can preview the association SQL predicate that will be used at runtime to access the related destination entity objects for a given instance of the source entity object.
On the Association Properties page, disable the Expose Accessor checkbox on either the Source or the Destination entity object when you want to create an association that represents a one-way relationship. The default, bidirectional navigation is more convenient for writing business validation logic, so in practice, you typically leave these default checkbox settings.
For example, Figure 4-6 shows an association that represents a bidirectional relationship, permitting either entity object to access the related entity row(s) on the other side when needed. In this example, this means that if you are working with an instance of an OrderEO
entity object, you can easily access the collection of its related OrderItemEO
rows. With any instance of a OrderItemEO
entity object, you can also easily access the Order
to which it belongs.
When you are satisfied with the association definition, click Finish.
When you create an association, JDeveloper creates an appropriate XML component definition file and saves it in the directory that corresponds to the name of its package. For example, if you created an association named OrderItemsOrdersFkAssoc
in the oracle.fodemo.storefront.entities.associations
subpackage, then the association XML file would be created in the ./oracle/fodemo/storefront/entities/associations
directory with the name OrderItemsOrdersFkAssoc.xml
. At runtime, the entity object uses the association information to automate working with related sets of entities.
You should consider the default settings for the accessor names on the Association Properties page and decide whether changing the names to something more intuitive is appropriate. The default settings define the names of the accessor attributes you will use at runtime to programmatically access the entities on the other side of the relationship. By default, the accessor names will be the names of the entity object on the other side. Since the accessor names on an entity must be unique among entity object attributes and other accessors, if one entity is related to another entity in multiple ways, then the default accessor names are modified with a numeric suffix to make the name unique.
In an existing association, you can rename the accessor using the Association Properties dialog.
It may be helpful to have an understanding of why you create associations. For more information, see Section 4.3, "Creating and Configuring Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To rename the entity accessor in an association:
In the Application Navigator, double-click the association.
In the overview editor, click the Relationships navigation tab.
On the Relationships page, expand the Accessors category and click the Edit icon.
The Association Properties dialog displays the current settings for the association's accessors.
Modify the name as necessary, and click OK to apply your changes and close the dialog.
Since associations are a component that you typically configure at the outset of your project and don't change frequently thereafter, you might want to move the associations to a different package so that your entity objects are easier to see. Both renaming components and moving them to a different package is straightforward using JDeveloper's refactoring functionality.
It may be helpful to have an understanding of why you create associations. For more information, see Section 4.3, "Creating and Configuring Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To move a set of business components to a different package:
In the Application Navigator, select the components you want to move.
Right-click one of the selected components, and choose Refactor > Move.
In the Move Business Components dialog, enter the name of the package to move the component(s) to, or click Browse to navigate to and select the package.
Click OK to apply your changes and close the dialog.
In the Application Navigator, right-click the component you want to rename, and choose Refactor > Rename.
In the Rename dialog, enter the new name for the component and click OK.
When you refactor ADF Business Components, JDeveloper moves the XML and Java files related to the components, and updates any other components that might reference them.
Figure 4-7 shows what the Application Navigator would look like after renaming all of the associations and moving them to the oracle.fodemo.storefront.associations
subpackage. While you can refactor the associations into any package name you choose, picking a subpackage keeps them logically related to the entities, and allows you to collapse the package of associations to better manage which files display in the Application Navigator.
You can associate a custom view object with the source end or destination end (or both) of an entity association.
When you traverse entity associations in your code, if the entities are not already in the cache, then the ADF Business Components framework performs a query to bring the entity (or entities) into the cache. By default, the query performed to bring an entity into the cache is the find-by-primary-key query that selects values for all persistent entity attributes from the underlying table. If the application performs a lot of programmatic entity association traversal, you could find that retrieving all of the attributes might be heavy-handed for your use cases.
Entity associations support the ability to associate a custom, entity-based view object with the source entity or destination entity in the association, or both. The primary entity usage of the entity-based view object you supply must match the entity type of the association end for which you use it.
Using a custom view object can be useful because the custom view object's query can include fewer columns and it can include an ORDER BY
clause. This allows you to control how much data is retrieved when an entity is brought into the cache due to association traversal, as well as the order in which any collections of related entities will appear.
For more information about creating a custom view object, see Section 42.8.2, "How to Create an Entity-Based Programmatic View Object."
A composition association represents a relationship between entities, such as Person
referenced by an Order
or a OrderItem
contained in a Order
. When you create composition associations, it is useful to know about the kinds of relationships you can represent, and the various options.
Associations between entity objects can represent two styles of relationships depending on whether the source entity:
References the destination entity
Contains the destination entity as a logical, nested part
Figure 4-8 depicts an application business layer that represents both styles of relationships. For example, an OrderEO
entry references a PersonEO
. This relationship represents the first kind of association, reflecting that a PersonEO
or an OrderEO
entity object can exist independent from each other. In addition, the removal of an Order
does not imply the cascade removal of the Person
to which it was referring.
In contrast, the relationship between OrderEO
and its collection of related OrderItemEO
details is stronger than a simple reference. The OrderItemEO
entries comprise a logical part of the overall OrderEO
. In other words, a OrderEO
is composed of OrderItemEO
entries. It does not make sense for a OrderItemEO
entity row to exist independently from an OrderEO
, and when an OrderEO
is removed — assuming the removal is allowed — all of its composed parts should be removed as well. This kind of logical containership represents the second kind of association, called a composition. The UML diagram in Figure 4-8 illustrates the stronger composition relationship using the solid diamond shape on the side of the association which composes the other side of the association.
The Business Components from Tables Wizard creates composition associations by default for any foreign keys that have the ON DELETE CASCADE
option. You can use the Create Association wizard or the overview editor for the association to indicate that an association is a composition association. Select the Composition Association checkbox on either the Association Properties page of the Create Association wizard or the Relationships page of the overview editor. An entity object offers additional runtime behavior in the presence of a composition. For the settings that control the behavior, see Section 4.10.13, "How to Configure Composition Behavior."
Since your layer of business domain objects represents a key reusable asset for your team, it is often convenient to visualize the business domain layer using a UML model. JDeveloper supports easily creating a diagram for your business domain layer that you and your colleagues can use for reference.
The UML diagram of business components is not just a static picture that reflects the point in time when you dropped the entity objects onto the diagram. Rather, it is a UML-based rendering of the current component definitions, that will always reflect the current state of affairs. What's more, the UML diagram is both a visualization aid and a visual navigation and editing tool. To open the overview editor for any entity object in a diagram, right-click the desired object and choose Properties from the context menu or double-click the desired object. You can also perform some entity object editing tasks directly on the diagram, like renaming entities and entity attributes, and adding or removing attributes.
To create a diagram of your entity objects, you can use the Create Business Components Diagram dialog, which is available in the New Gallery.
It may be helpful to have an understanding of how entity diagrams are used in the application. For more information, see Section 4.4, "Creating an Entity Diagram for Your Business Layer."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create an entity diagram that models existing entity objects:
In the Application Navigator, right-click the project in which you want to create the entity diagram and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Business Components Diagram, and click OK.
In the dialog, do the following to create the diagram:
Enter a name for the diagram, for example Business Domain Objects
.
Enter the package name in which the diagram will be created. For example, you might create it in a subpackage like myproject.model.design
.
Click OK.
To add existing entity objects to the diagram, select them in the Application Navigator and drop them onto the diagram surface.
After you have created the diagram you can use the Property Inspector to adjust visual properties of the diagram. For example you can:
Hide or show the package name
Change the font
Toggle the grid and page breaks on or off
Display association names that may otherwise be ambiguous
You can also create an image of the diagram in PNG
, JPG
, SVG
, or compressed SVG
format, by choosing Publish Diagram from the context menu on the diagram surface.
Figure 4-9 shows a sample diagram that models various entity objects from the business domain layer.
When you create a business components diagram, JDeveloper creates an XML file *.oxd_bc4j
representing the diagram in a subdirectory of the project's model path that matches the package name in which the diagram resides.
By default, the Application Navigator unifies the display of the project contents paths so that ADF components and Java files in the source path appear in the same package tree as the UML model artifacts in the project model path. However, as shown in Figure 4-10, using the Navigator Display Options toolbar button on the Application Navigator, you can see the distinct project content path root directories when you prefer.
When you include a business component like an entity object to a UML diagram, JDeveloper adds extra metadata to a <Data>
section of the component's XML component descriptor as shown in Example 4-2. This additional information is used at design time only.
Example 4-2 Additional UML Metadata Added to an Entity Object XML Descriptor
<Entity Name="OrderEO" ... > <Data> <Property Name ="COMPLETE_LIBRARY" Value ="FALSE" /> <Property Name ="ID" Value ="ff16fca0-0109-1000-80f2-8d9081ce706f::::EntityObject" /> <Property Name ="IS_ABSTRACT" Value ="FALSE" /> <Property Name ="IS_ACTIVE" Value ="FALSE" /> <Property Name ="IS_LEAF" Value ="FALSE" /> <Property Name ="IS_ROOT" Value ="FALSE" /> <Property Name ="VISIBILITY" Value ="PUBLIC" /> </Data> : </Entity>
On an entity diagram, the names of entity objects, attributes, and associations can be changed for clarity. Changing names on a diagram does not affect the underlying data names. The name change persists for the diagram only. The new name may contain spaces and mixed case for readability. To change the actual entity object names, attribute names, or association names, open the entity object or association in the overview editor.
A property set is a named collection of properties, where a property is defined as a name/value pair. Property sets are a convenience mechanism to group properties and then reference them from other ADF Business Components objects. Properties defined in a property set can be configured to be translatable, in which case the translations are stored in a message bundle file owned by the property set.
Property sets can be used for a variety of functions, such as control hints and error messages. A property set may contain control hints and other custom properties, and you can associate them with multiple attributes of different objects.
Note:
Take care when defining property sets that contain translatable content. Be sure not to "overload" common terms in different contexts. For example, the term "Name" might be applied to both an object and a person in one language, but then translated into two different terms in a target language. Even though a term in several contexts might be the same in the source language, a separate distinguishable term should be used for each context.Property sets can be used with entity objects and their attributes, view objects and their attributes, and application modules.
To define a property set, you create a new property set using a dialog and then specify properties using the Property Inspector.
It may be helpful to have an understanding of how property sets can be used. For more information, see Section 4.5, "Defining Property Sets."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
In the Application Navigator, right-click the project where you want to create the property set, and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Property Set, and click OK.
In the Create Property Set dialog, enter the name and location of the property set and click OK.
From the View menu, choose Property Inspector.
In the Property Inspector, define the properties for the property set.
After you have created the property set, you can apply the property set to an entity object or attribute, and use the defined properties (or override them, if necessary).
It may be helpful to have an understanding of how property sets can be used. For more information, see Section 4.5, "Defining Property Sets."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To apply a property set to an entity object or view object:
In the Application Navigator, double-click the desired object (entity object or view object).
In the overview editor, click the General navigation tab and then click the Edit icon next to the Property Set line.
Select the appropriate property set, and click OK.
To apply a property set to an attribute:
In the Application Navigator, double-click the object (entity object or view object) that contains the attribute.
In the overview editor, click the Attributes navigation tab, select the attribute you want to edit, and then click the Details tab.
In the Property Set dropdown list, select the appropriate property set.
If you are familiar with previous versions of ADF Business Components, you may have used control hints. Control hints allow you to define label text, tooltip, and format mask hints for entity object attributes. The UI hints you define on your business domain layer are inherited by any entity-based view objects as well. You can also set additional control hints on view objects and application modules in a similar manner.
To add attribute control hints to an entity object, use the overview editor.
It may be helpful to have an understanding of how control hints are used in an entity object. For more information, see Section 4.6, "Defining Attribute Control Hints for Entity Objects."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To add attribute control hints to an entity object:
In the Application Navigator, double-click the desired entity object.
In the overview editor, click the Attributes navigation tab, select the attribute you want to edit, and then click the UI Hints tab.
Specify control hints as necessary.
For example, Figure 4-12 shows control hints defined for the attribute ExpireDate
of the PaymentOptionEO
entity object. The defined hints include the following:
Format Type of Simple Date
Format mask of mm/yy
Note:
Java defines a standard set of format masks for numbers and dates that are different from those used by the Oracle database's SQL and PL/SQL languages. For reference, see the Javadoc for thejava.text.DecimalFormat
and java.text.SimpleDateFormat
classes.When you define attribute control hints for an entity object, JDeveloper creates a resource bundle file in which to store them. The hints that you define can be used by generated forms and tables in associated view clients. The type of file and its granularity are determined by Resource Bundle options in the Project Properties dialog. For more information, see Section 4.7, "Working with Resource Bundles."
When you set the Format Type control hint (on the UI Hints tab) for an attribute (for example, to Simple Date), you can also specify a format mask for the attribute to customize how the UI displays the value. If the mask you want to use is not listed in the Format dropdown list, you can simply type it into the field.
Not all formatters require format masks. Specifying a format mask is only needed if that formatter type requires it. For example, the date formatter requires a format mask, but the currency formatter does not. In fact the currency formatter does not support format mask at all.
The mask elements that you can use are defined by the associated Java format class. For information about the mask elements for the Simple Date format type, see the Javadoc for java.text.SimpleDateFormat
. For information about the mask elements for the Number format type, see the Javadoc for java.text.DecimalFormat
.
If you have a format mask that you will continue to use on multiple occasions, you can add it to the formatinfo.xml
file, so that it is available from the Format dropdown list on the UI Hints tab. The entries in this file define the format masks and formatter classes for a domain class. Example 4-3 shows the format definitions for the java.util.Date
domain.
Note:
You can find theformatinfo.xml
file in the BC4J subdirectory of the JDeveloper system directory (for example, C:\Documents and Settings\
username
\Application Data\JDeveloper\
system##
\o.BC4J\formatinfo.xml
).Example 4-3 Format Definitions for java.util.Date in formatinfo.xml
<?xml version="1.0"?><FORMATTERS> . . . <DOMAIN CLASS="java.util.Date"> <FORMATTER name="Simple Date" class="oracle.jbo.format.DefaultDateFormatter"> <FORMAT text="yyyy-MM-dd" /> <FORMAT text="EEE, MMM d, ''yy" /> <FORMAT text="dd-MM-yy" /> <FORMAT text="dd-MMM-yyyy" /> <FORMAT text="dd/MMM/yyyy" /> </FORMATTER> </DOMAIN> . . . </FORMATTERS>
The definition of the format mask belongs to a formatter and a domain class, and includes the text specification of the mask as it appears on the UI Hints tab. When you specify the Format Type (FORMATTER name
) for an attribute of a given type (DOMAIN CLASS
), the masks (FORMAT text
) appear in the Format dropdown list.
To map a formatter to a domain for use with control hints, you can either amend one of the default formatters provided in the oracle.jbo.format
package, or create a new formatter class by extending the oracle.jbo.format.Formatter
class. The default formatters provided with JDeveloper aggregate the formatters provided in the java.text
package.
It is not necessary to create new domain to map a formatter. You can use an existing domain when the business components project contains a domain of the same data type as the formatter.
It may be helpful to have an understanding of how control hints are used in an entity object. For more information, see Section 4.6, "Defining Attribute Control Hints for Entity Objects."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
Open the formatinfo.xml
file in a text editor.
Find the domain class and formatter name for which you want to add a format mask.
Insert a new FORMAT
entry within the FORMATTER
element.
After defining a format mask, you can select the new format mask from the Format dropdown list on the UI Hints tab.
Note:
If you create a new domain for the format mask, the XML definition of the formatter must include aDOMAIN CLASS
(which can be a new or existing one), the FORMATTER
(which includes the name and class), and the list of FORMAT
definitions the formatter class specifies.When you define translatable strings (such as validator error messages, or attribute control hints for an entity object or view object), by default JDeveloper creates a project-level resource bundle file in which to store them. For example, when you define control hints for an entity object in the StoreFront
project, JDeveloper creates the message bundle file named StoreFrontBundle.
xxx
for the package. The hints that you define can be used by generated forms and tables in associated view clients.
The resource bundle option that JDeveloper uses is determined by an option on the Resource Bundle page of the Project Properties dialog. By default JDeveloper sets the option to Properties Bundle, which produces a .properties
file. For more information on this and other resource bundle options, see Section 4.7.1, "How to Set Message Bundle Options."
You can inspect the message bundle file for the entity object by selecting the object in the Application Navigator and looking in the corresponding Sources node in the Structure window. The Structure window shows the implementation files for the component you select in the Application Navigator.
Example 4-4 shows a sample message bundle file where the control hint information appears. The first entry in each String
array is a message key; the second entry is the locale-specific String
value corresponding to that key.
Example 4-4 Project Message Bundle Stores Locale-Sensitive Control Hints
AddressUsageEO_OwnerTypeCode_Error_0=Invalid OwnerTypeCode. AddressUsageEO_UsageTypeCode_Error_0=Invalid UsageTypeCode. OwnerTypeCode_CONTROLTYPE=105 PaymentOptionEO_RoutingIdentifier_Error_0=Please enter a valid routing identifier. PaymentOptionsEO_PaymentTypeCode_Error_0=Invalid PaymentTypeCode. PaymentTypeCode_CONTROLTYPE=105 PaymentOption_AccountNumber=Please enter a valid Account Number MinPrice_FMT_FORMATTER=oracle.jbo.format.DefaultCurrencyFormatter CostPrice_FMT_FORMATTER=oracle.jbo.format.DefaultCurrencyFormatter UnitPrice_FMT_FORMATTER=oracle.jbo.format.DefaultCurrencyFormatter OrderEO_GiftMessage=Please supply a message shorter than 200 characters OrderEO=Please supply a gift message DiscountBaseEO_DiscountAmount=Discount must be between 0 and 40% oracle.fodemo.storefront.entities.PaymentOptionEO.ExpireDate_FMT_FORMAT=mm/yy #Date range validation for ValidFrom and ValidTo dates PaymentOptionEO_invalidDateRange_Error_0=Date range is invalid. {0} must be greater than {1}. PaymentOptionEO_DateRange_Error_0=Invalid date range.{0} should be greater than {1}. oracle.fodemo.storefront.entities.PaymentOptionEO.ValidFromDate_LABEL=Valid From Date oracle.fodemo.storefront.entities.PaymentOptionEO.ValidToDate_LABEL=Valid To Date OrderItemsVO_ImageId_Rule_0=ImageId not found oracle.fodemo.storefront.store.queries.AddressesVO.Address1_LABEL=Address oracle.fodemo.storefront.store.queries.AddressesVO.PostalCode_LABEL=Post Code or ZIP . . .
The resource bundle option JDeveloper uses to save control hints and other translatable strings is determined by an option on the Resource Bundle page of the Project Properties dialog. By default JDeveloper sets the option to Properties Bundle which produces a .properties
file.
It may be helpful to have an understanding of how resource bundles are used. For more information, see Section 4.7, "Working with Resource Bundles."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To set resource bundle options for your project
In the Application Navigator, right-click the project and choose Project Properties.
Click Resource Bundle.
Select whether to use project or custom settings.
If you select Use Custom Settings, the settings apply only to your work with the current project. They are preserved between sessions, but are not recorded with the project and cannot be shared with other users. If you select Use Project Settings, your choices are recorded with the project and can be shared with others who use the project.
Specify your preference with the following options by selecting or deselecting the option:
Automatically Synchronize Bundle
Warn About Hard-coded Translatable Strings
Always Prompt for Description
For more information on these options, click Help to see the online help.
Select your choice of resource bundle granularity.
One Bundle Per Project (default)
One Bundle Per File
Multiple Shared Bundles (not available for ADF Business Components)
Select the type of file to use.
List Resource Bundle
The ListResourceBundle
class manages resources in a name/value array. Each ListResourceBundle class is contained within a Java class file. You can store any locale-specific object in a ListResourceBundle class.
Properties Bundle (default)
A text file containing translatable text in name/value pairs. Property files (like the one shown in Example 4-4) can contain values only for String objects. If you need to store other types of objects, you must use a ListResourceBundle instead.
Xliff Resource Bundle
The XML Localization Interchange File Format (XLIFF) is an XML-based format for exchanging localization data.
Click OK to apply your settings and close the dialog.
When you define translatable strings (for example, for attribute control hints), the Select Text Resource dialog allows you to enter a new string or select one that is already defined in the default resource bundle for the object. You can also use a different resource bundle if necessary. This is helpful when you use a common resource bundle that is shared between projects.
It may be helpful to have an understanding of how resource bundles are used. For more information, see Section 4.7, "Working with Resource Bundles."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To use strings in a nondefault resource bundle:
In the Select Text Resource dialog, select the bundle you want to use from the Resource Bundle dropdown list.
If the desired resource bundle is not included in the Resource Bundle dropdown list, click the Browse icon to locate and select the resource bundle you want to use.
The dialog displays the strings that are currently defined in the selected resource bundle.
Select an existing string and click Select, or enter a new string and click Save and Select.
If you entered a new string it is written to the selected resource bundle.
Internationalizing the model layer of an application built using ADF Business Components entails producing translated versions of each component message bundle file. For example, the Italian version of the OrdersImplMsgBundle
message bundle would be a class named OrdersImplMsgBundle_it
and a more specific Swiss Italian version would have the name OrdersImplMsgBundle_it_ch
. These classes typically extend the base message bundle class, and contain entries for the message keys that need to be localized, together with their localized translation.
Example 4-5 shows the Italian version of an entity object message bundle. Notice that in the Italian translation, the format masks for RequestDate
and AssignedDate
have been changed to dd/MM/yyyy HH:mm
. This ensures that an Italian user will see a date value like May 3rd, 2006, as 03/05/2006 15:55
, instead of 05/03/2006 15:55
, which the format mask in the default message bundle would produce. Notice the overridden getContents()
method. It returns an array of messages with the more specific translated strings merged together with those that are not overridden from the superclass bundle. At runtime, the appropriate message bundles are used automatically, based on the current user's locale settings.
Example 4-5 Localized Entity Object Component Message Bundle for Italian
package devguide.model.entities.common; import oracle.jbo.common.JboResourceBundle; public class ServiceRequestImplMsgBundle_it extends ServiceRequestImplMsgBundle { static final Object[][] sMessageStrings = { { "AssignedDate_FMT_FORMAT", "dd/MM/yyyy HH:mm" }, { "AssignedDate_LABEL", "Assegnato il" }, { "AssignedTo_LABEL", "Assegnato a" }, { "CreatedBy_LABEL", "Aperto da" }, { "ProblemDescription_LABEL", "Problema" }, { "RequestDate_FMT_FORMAT", "dd/MM/yyyy HH:mm" }, { "RequestDate_LABEL", "Aperto il" }, { "RequestDate_TOOLTIP", "La data in cui il ticket è stato aperto" }, { "Status_LABEL", "Stato" }, { "SvrId_LABEL", "Ticket" } }; public Object[][] getContents() { return super.getMergedArray(sMessageStrings, super.getContents()); } }
Business logic groups allow you to encapsulate a set of related control hints, default values, and validation logic. A business logic group is maintained separate from the base entity in its own file, and can be enabled dynamically based on context values of the current row.
This is useful, for example, for an HR application that defines many locale-specific validations (like national identifier or tax law checks) that are maintained by a dedicated team for each locale. The business logic group eases maintenance by storing these validations in separate files, and optimizes performance by loading them only when they are needed.
Each business logic group contains a set of business logic units. Each unit identifies the set of business logic that is loaded for the entity, based on the value of the attribute associated with the business logic group.
For example, you can define a business logic group for an Employee
entity object, specifying the EmpRegion
attribute as the discriminator. Then define a business logic unit for each region, one that specifies a range validator for the employee's salary. When the application loads a row from the Employee
entity, the appropriate validator for the EmpSalary
attribute is loaded (based on the value of the EmpRegion
attribute).
In another example, from the StoreFront module of the Fusion Order Demo application, the PersonEO
entity object has a business logic group called PersonTypeCodeGroup
that uses PersonTypeCode
as the discriminator attribute. Because this attribute has three valid values (CUST
, STAFF
, and SUPP
), there are three corresponding business logic units.
In this scenario, each business logic unit contains new or modified business logic that pertains only to that person type:
The CUST
business logic unit contains logic that pertains to customers. For example, it contains a validator that checks for a phone number because all customers must have a phone number.
The STAFF
business logic unit contains logic that pertains to staff members. For example, it contains a validator that constrains the credit limit.
The SUPP
business logic unit contains logic that pertains to suppliers. For example, it contains a validator that makes sure the ContactByAffiliatesFlag
attribute is set to N
, because suppliers cannot be contacted by affiliates.
You create the business logic group for an entity object from the overview editor.
It may be helpful to have an understanding of how business logic groups are used. For more information, see Section 4.8, "Defining Business Logic Groups."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create a business logic group:
In the Application Navigator, double-click the entity for which you want to create a business logic group.
In the overview editor, click the General navigation tab.
On the General page, expand the Business Logic Groups section, and click the Add icon.
In the creation dialog, select the appropriate group discriminator attribute and specify a name for the group.
Tip:
To enhance the readability of your code, you can name the group to reflect the discriminator. For example, if the group discriminator attribute isPersonTypeCode
, you can name the business logic group PersonTypeCodeGroup
.Click OK.
The new business logic group is added to the table in the overview editor. After you have created the group, you can add business logic units to it.
You can create a business logic unit from the New Gallery, or directly from the context menu of the entity that contains the business logic group.
It may be helpful to have an understanding of how business logic groups are used. For more information, see Section 4.8, "Defining Business Logic Groups."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create a business logic unit:
In the Application Navigator, right-click the entity that contains the business logic group and choose New Entity Business Logic Unit from the context menu.
In the Create Business Logic Unit dialog, specify the name of the base entity and select the appropriate business logic group.
Enter a name for the business logic unit.
The name of each business logic unit must reflect a valid value of the group discriminator attribute with which this business logic unit will be associated. For example, if the group discriminator attribute is PersonTypeCode
, the name of the business logic unit associated with the PersonTypeCode
value of STAFF
must be STAFF
.
Specify the package for the business logic unit.
Note:
The package for the business logic unit does not need to be the same as the package for the base entity or the business logic group. This allows you to develop and deliver business logic units separately from the core application.Click OK.
JDeveloper creates the business logic unit and opens it in the overview editor. The name displayed for the business logic unit in the Application Navigator contains the name of the entity object and business logic group in the format EntityName_BusLogicGroupName_BusLogicUnitName
. For example, when you create a business logic unit with the name CUST
in the PersonTypeCodeGroup
business logic group of the PersonEO
entity object, the displayed name of the business logic unit is PersonEO_PersonTypeCodeGroup_CUST
.
After you have created the unit, you can redefine the business logic for it.
After you have created a business logic unit, you can open it in the overview editor and add business logic (such as adding an entity-level validator) just as you would in the base entity.
It may be helpful to have an understanding of how business logic groups are used. For more information, see Section 4.8, "Defining Business Logic Groups."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To add an entity validator to a business logic unit:
In the Application Navigator, double-click the business logic unit.
In the overview editor, click the Business Rules navigation tab.
On the Business Rules page, select the Entity Validators node and click the Add icon.
Define your validation rule, and click OK.
For example, the PersonEO
entity object in the StoreFront module of the Fusion Order Demo application has a business logic unit called PersonEO_PersonTypeCodeGroup_CUST
. This business logic unit has an entity validator that checks for the presence of a phone number to ensure that all persons who are customers have a phone number.
When you view the Attributes page for the business logic unit (in the overview editor), you can see that the Extends column in the attributes table shows that the attributes are "extended" in the business logic unit. Extended attributes are editable only in the base entity, not in the business logic unit. To implement changes in the business logic unit rather than the base entity, you must define attributes as overridden in the business logic unit before you edit them.
It may be helpful to have an understanding of how business logic groups are used. For more information, see Section 4.8, "Defining Business Logic Groups."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To override attributes in a business logic unit:
In the Application Navigator, double-click the business logic unit.
In the overview editor, click the Attributes navigation tab.
On the Attributes page, select the desired attribute and click the Override button.
After you make an attribute overridden, you can edit the attribute as you normally would in the tabs below the table. You will notice that in an overridden attribute, you are limited to making modifications to only control hints, validators, and default values.
When you create a business logic group, JDeveloper adds a reference to the group in the base entity's XML file. Example 4-6 shows the code added to the base entity's XML file for the business logic group.
Example 4-6 XML Code in the Base Entity for a Business Logic Group
<BusLogicGroup Name="PersonTypeCodeGroup" DiscrAttrName="PersonTypeCode"/>
When you create a business logic unit, JDeveloper generates an XML file similar to that of an entity object. Example 4-7 shows XML code for a business logic unit.
Note:
The package for the business logic unit does not need to be the same as the package for the base entity or the business logic group. This allows you to develop and deliver business logic units separately from the core application.Example 4-7 XML Code for a Business Logic Unit
<Entity xmlns="http://xmlns.oracle.com/bc4j" Name="PersonEO_PersonTypeCodeGroup_CUST" Version="11.1.1.54.6" Extends="oracle.fodemo.storefront.entities.PersonEO" DBObjectType="table" DBObjectName="PERSONS" BindingStyle="OracleName" UseGlueCode="false" BusLogicGroupName="PersonTypeCodeGroup" BusLogicUnitName="CUST" xmlns:validation="http://xmlns.oracle.com/adfm/validation"> <DesignTime> <Attr Name="_codeGenFlag2" Value="Access"/> <AttrArray Name="_publishEvents"/> </DesignTime> <validation:ExpressionValidationBean Name="PersonEO_PersonTypeCodeGroup_CUST_Rule_0" OperandType="EXPR" Inverse="false"> <validation:MsgIds> <validation:Item Value="CUST_PHONE_REQUIRED"/> </validation:MsgIds> <validation:TransientExpression> <![CDATA[if (PhoneNumber == null && MobilePhoneNumber == null) return false; else return true;]]> </validation:TransientExpression> </validation:ExpressionValidationBean> <ResourceBundle> <PropertiesBundle PropertiesFile="oracle.fodemo.storefront.entities.common.PersonEO_PersonTypeCodeGroup_CUSTMsgBundle"/> </ResourceBundle> </Entity>
When a row is loaded in the application at runtime, the entity object decides which business logic units to apply to it.
The base entity maintains a list of business logic groups. Each group references the value of an attribute on the entity, and this value determines which business logic unit to load for that group. This evaluation is performed for each row that is loaded.
If the logic for determining which business logic unit to load is more complex than just a simple attribute value, you can create a transient attribute on the entity object, and use a Groovy expression to determine the value of the transient attribute.
Entity objects offer numerous declarative features to simplify implementing typical enterprise business applications. Depending on the task, sometimes the declarative facilities alone may satisfy your needs. The declarative runtime features that describe the basic persistence features of an entity object are covered in this section, while declarative validation and business rules are covered in Chapter 7, "Defining Validation and Business Rules Declaratively."
Note:
It is possible to go beyond the declarative behavior to implement more complex business logic or validation rules for your business domain layer when needed. In Chapter 8, "Implementing Validation and Business Rules Programmatically," you'll see some of the most typical ways that you extend entity objects with custom code.Also, it is important to note as you develop your application that the business logic you implement, either programmatically or declaratively, should not assume that the attributes of an entity object or view row will be set in a particular order. This will cause problems if the end user enters values for the attributes in an order other than the assumed one.
To configure the declarative runtime behavior of an entity object, use the overview editor.
It may be helpful to have an understanding of declarative configuration of runtime behavior. For more information, see Section 4.9, "Configuring Runtime Behavior Declaratively."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To configure the declarative runtime behavior of an entity object:
In the Application Navigator, double-click an entity object.
In the overview editor, click the General navigation tab to view the name and package of the entity object, and configure aspects of the object at the entity level, such as its associated schema, alternative keys, custom properties, and security.
The Alternate Keys section allows you to select entity object attributes mapped to the database that can serve as an alternative primary key. For information on alternative keys, see Section 4.10.15, "How to Define Alternate Key Values."
The Tuning section allows you to set options to make database operations more efficient when you create, modify, or delete multiple entities of the same type in a single transaction. For more information, see Section 4.9.3, "How to Use Update Batching."
The Custom Properties section allows you to define custom metadata that you can access at runtime on the entity.
The Security section allows you to define role-based updatability permissions for the entity. For more information, see Chapter 35, "Enabling ADF Security in a Fusion Web Application."
The Business Logic Groups section allows you to add and edit business logic groups. For more information, see Section 4.8, "Defining Business Logic Groups."
Click the Attributes navigation tab to create or delete attributes that represent the data relevant to an entity object, and configure aspects of the attribute, such as validation rules, custom properties, and security.
Select an attribute and click the Edit icon to access the properties of the attribute. For information on how to set these properties, see Section 4.10, "Setting Attribute Properties."
Tip:
If your entity has a long list of attribute names, there's a quick way to find the one you're looking for. In the Structure window with the Attributes node expanded, you can begin to type the letters of the attribute name and JDeveloper performs an incremental search to take you to its name in the tree.Click the Business Rules navigation tab to define declarative validators for the entity object and its attributes. For more information, see Chapter 7, "Defining Validation and Business Rules Declaratively."
Click the Java navigation tab to select the classes you generate for custom Java implementation. You can use the Java classes for such things as defining programmatic business rules, as in Chapter 8, "Implementing Validation and Business Rules Programmatically."
Click the Business Events navigation tab to define events that your entity object can use to notify others of interesting changes in its state, optionally including some or all of the entity object's attributes in the delivered event. For more information about business events, see Section 4.12, "Creating Business Events."
Click the View Accessors navigation tab to create and manage view accessors. For more information, see Section 10.4.1, "How to Create a View Accessor for an Entity Object or View Object."
The declarative settings that describe and control an entity object's runtime behavior are stored in its XML component definition file. When you use the overview editor to modify settings of your entity, JDeveloper updates the component's XML definition file and optional custom Java files.
You can use update batching to reduce the number of DML statements issued with multiple entity modifications.
By default, the ADF Business Components framework performs a single DML statement (INSERT
, UPDATE
, DELETE
) for each modified entity of a given entity definition type. For example, say you have an Employee entity object type for which multiple instances are modified during typical use of the application. If two instances were created, three existing instances modified, and four existing instances deleted, then at transaction commit time the framework issues nine DML statements (2 INSERT
s, 3 UPDATE
s, and 4 DELETE
s) to save these changes.
If you will frequently be updating more than one entity of a given type in a transaction, consider using the update batching feature for that entity definition type. In the example, update batching (with a threshold of 1) causes the framework to issue just three DML statements: one bulk INSERT
statement processing two inserts, one bulk UPDATE
statement processing three updates, and one bulk DELETE
statement processing four deletes.
Note:
If the entity object has any attributes that are set to Refresh After Insert or Refresh After Update, then the batch update feature is disabled.It may be helpful to have an understanding of the declarative configuration of runtime behavior. For more information, see Section 4.9, "Configuring Runtime Behavior Declaratively."
You may also find it useful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To enable update batching for an entity
In the Application Navigator, double-click the appropriate entity.
In the overview editor, click the General navigation tab.
On the General page of the overview editor, expand the Tuning section, select the Use Update Batching checkbox, and specify the appropriate threshold.
This establishes a batch processing threshold beyond which Oracle ADF will process the modifications in a bulk DML operation.
The declarative framework helps you set attribute properties easily. In all cases, you set these properties on the Attributes page of the overview editor.
The Persistent
property controls whether the attribute value corresponds to a column in the underlying table, or whether it is just a transient value. If the attribute is persistent, the Database Column area lets you change the name of the underlying column that corresponds to the attribute and indicate its column type with precision and scale information (e.g. VARCHAR2(40)
or NUMBER(4,2)
). Based on this information, at runtime the entity object enforces the maximum length and precision/scale of the attribute value, and throws an exception if a value does not meet the requirements.
Both the Business Components from Tables wizard and the Create Entity Object wizard infer the Java type of each entity object attribute from the SQL type of the database column type of the column to which it is related.
Note:
The project's Type Map setting also plays a role in determining the Java data type. You specify the Type Map setting when you initialize your business components project, before any business components are created. For more information, see Section 3.3.1, "Choosing a Connection, SQL Platform, and Data Type Map."The Type field (on the Details tab) allows you to change the Java type of the entity attribute to any type you might need. The Column Type field reflects the SQL type of the underlying database column to which the attribute is mapped. The value of the Column Name field controls the column to which the attribute is mapped.
Your entity object can handle tables with various column types, as listed in Table 4-1. With the exception of the java.lang.String
class, the default Java attribute types are all in the oracle.jbo.domain
and oracle.ord.im
packages and support efficiently working with Oracle database data of the corresponding type. The dropdown list for the Type field includes a number of other common Java types that are also supported.
Table 4-1 Default Entity Object Attribute Type Mappings
Oracle Column Type | Entity Column Type | Entity Java Type |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note:
In addition to the types mentioned here, you can use any Java object type as an entity object attribute's type, provided it implements thejava.io.Serializable
interface.When working with types that support defining a maximum length like VARCHAR2(n)
, the Column Type field (on the Details tab) includes the maximum attribute length as part of the value. For example, an attribute based on a VARCHAR2(10)
column in the database will initially reflect the maximum length of 10 characters by showing VARCHAR2(10)
as the database column type. If for some reason you want to restrict the maximum length of the String
-valued attribute to fewer characters than the underlying column will allow, just change the maximum length of the Column Type value.
For example, if the EMAIL
column in the PERSONS
table is VARCHAR2(50)
, then by default the Email
attribute in the Persons
entity object defaults to the same. But if you know that the actual email addresses are always 8 characters or fewer, you can update the database column type for the Email
attribute to be VARCHAR2(8)
to enforce a maximum length of 8 characters at the entity object level.
The same holds for attributes related to database column types that support defining a precision and scale like NUMBER(p[
,s
])
. For example, to restrict an attribute based on a NUMBER(7,2)
column in the database to instead have a precision of 5 and a scale of 1, just update the value of the Column Type field to be NUMBER(5,1)
.
The Updatable
property controls when the value of a given attribute can be updated. You can select the following values:
Always, the attribute is always updatable
Never, the attribute is read-only
While New, the attribute can be set during the transaction that creates the entity row for the first time, but after being successfully committed to the database the attribute is read-only
Note:
In addition to the static declaration of updatability, you can also add custom code in theisAttributeUpdateable()
method of the entity to determine the updatability of an attribute at runtime.Select the Mandatory checkbox if the field is required. The mandatory property is enforced during entity-level validation at runtime (and not when the attribute validators are run).
The Primary Key
property indicates whether the attribute is part of the key that uniquely identifies the entity. Typically, you use a single attribute for the primary key, but multiattribute primary keys are fully supported.
At runtime, when you access the related Key
object for any entity row using the getKey()
method, this Key
object contains the value of the primary key attribute for the entity object. If your entity object has multiple primary key attributes, the Key
object contains each of their values. It is important to understand that these values appear in the same relative sequential order as the corresponding primary key attributes in the entity object definition.
For example, if the OrderItemEO
entity object has multiple primary key attributes OrderId
and LineItemId
. On the Entity Attribute page of the overview editor, OrderId
is first, and LineItemId
is second. An array of values encapsulated by the Key
object for an entity row of type OrderItemEO
will have these two attribute values in exactly this order.
It is crucial to be aware of the order in which multiple primary key attributes appear on the Entity Attributes page. If you try to use findByPrimaryKey()
to find an entity with a multiattribute primary key, and the Key
object you construct has these multiple primary key attributes in the wrong order, the entity row will not be found as expected.
In addition, to populate the primary key in new rows, you might want to use a trigger to assign the value from the database. For more information, see Section 4.10.10, "How to Get Trigger-Assigned Primary Key Values from a Database Sequence"
The value field on the Details tab allows you to specify a static default value for the attribute when the value type is set to Literal. For example, you can set the default value of the ServiceRequest
entity object's Status
attribute to Open
, or set the default value of the User
entity object's UserRole
attribute to user
.
Note:
When more than one attribute is defaulted for an entity object, the attributes are defaulted in the order in which they appear in the entity object's XML file.You can use a Groovy expression to define a default value for an attribute. This approach is useful if you want to be able to change default values at runtime, but if the default value is always the same, the value is easier to see and maintain using a value field with the Literal type (on the Details tab). For general information about using Groovy, see Section 3.6, "Overview of Groovy Scripting Language Support."
It may be helpful to have an understanding of how you set attribute properties. For more information, see Section 4.10, "Setting Attribute Properties."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To define a default value using a Groovy expression:
In the Application Navigator, double-click the desired entity object.
In the overview editor, click the Attributes navigation tab.
On the Attributes page, select the attribute you want to edit, and then click the Details tab.
Select Expression for the value type, and click the Edit icon (next to the value field).
Enter a Groovy expression in the field provided, and click OK.
When you define a default value using a Groovy expression, a <TransientExpression>
tag is added to the entity object's XML file within the appropriate attribute. Example 4-8 shows sample XML code for a Groovy expression that returns the current date for a default value.
If you know that the underlying column value will be updated by a database trigger during insert or update operations, you can enable the respective Refresh on Insert or Refresh on Update checkboxes on the Details tab to ensure that the framework automatically retrieves the modified value and keeps the entity object and database row in sync. The entity object will use the Oracle SQL RETURNING INTO
feature, while performing the INSERT
or UPDATE
to return the modified column back to your application in a single database roundtrip.
Note:
If you create an entity object for a synonym that resolves to a remote table over a DBLINK, use of this feature will give an error at runtime like:JBO-26041: Failed to post data to database during "Update" ## Detail 0 ## ORA-22816: unsupported feature with RETURNING clause
Section 4.18, "Basing an Entity Object on a Join View or Remote DBLink" describes a technique to circumvent this database limitation.
One common case for refreshing an attribute after insert occurs when a primary key attribute value is assigned by a BEFORE INSERT FOR EACH ROW
trigger. Often the trigger assigns the primary key from a database sequence using PL/SQL logic. Example 4-9 shows an example of this.
Example 4-9 PL/SQL Code Assigning a Primary Key from a Database Sequence
CREATE OR REPLACE TRIGGER ASSIGN_SVR_ID BEFORE INSERT ON SERVICE_REQUESTS FOR EACH ROW BEGIN IF :NEW.SVR_ID IS NULL OR :NEW.SVR_ID < 0 THEN SELECT SERVICE_REQUESTS_SEQ.NEXTVAL INTO :NEW.SVR_ID FROM DUAL; END IF; END;
On the Details tab (on the Attributes page of the overview editor), you can set the value of the Type field to the built-in data type named DBSequence
, and the primary key will be assigned automatically by the database sequence. Setting this data type automatically selects the Refresh on Insert checkbox.
Note:
The sequence name shown on the Sequence tab is used only at design time when you use the Create Database Tables feature described in Section 4.2.6, "How to Create Database Tables from Entity Objects." The sequence indicated here will be created along with the table on which the entity object is based.When you create a new entity row whose primary key is a DBSequence
, a unique negative number is assigned as its temporary value. This value acts as the primary key for the duration of the transaction in which it is created. If you are creating a set of interrelated entities in the same transaction, you can assign this temporary value as a foreign key value on other new, related entity rows. At transaction commit time, the entity object issues its INSERT
operation using the RETURNING INTO
clause to retrieve the actual database trigger-assigned primary key value. In a composition relationship, any related new entities that previously used the temporary negative value as a foreign key will get that value updated to reflect the actual new primary key of the master.
You will typically also set the Updatable property of a DBSequence-valued primary key to Never. The entity object assigns the temporary ID, and then refreshes it with the actual ID value after the INSERT
operation. The end user never needs to update this value.
For information on how to implement this functionality for an association that is not a composition, see Section 4.14.7.3.3, "Associations Based on DBSequence-Valued Primary Keys."
Note:
For a metadata-driven alternative to the DBSequence approach, see Section 4.14.5, "Assigning the Primary Key Value Using an Oracle Sequence."At runtime, the framework provides automatic "lost update" detection for entity objects to ensure that a user cannot unknowingly modify data that another user has updated and committed in the meantime. Typically, this check is performed by comparing the original values of each persistent entity attribute against the corresponding current column values in the database at the time the underlying row is locked. Before updating a row, the entity object verifies that the row to be updated is still consistent with the current state of the database. If the row and database state are inconsistent, then the entity object raises the RowInconsistentException
.
You can make the lost update detection more efficient by identifying any attributes of your entity whose values you know will be updated whenever the entity is modified. Typical candidates include a version number column or an updated date column in the row. The change-indicator attribute's value might be assigned by a database trigger you've written and refreshed in the entity object, because you selected the Refresh on Insert or Refresh on Update option (on the Details tab). Alternatively, you can indicate that the entity object should manage updating the change-indicator attribute's value using the history attribute feature described in Section 4.10.12, "How to Track Created and Modified Dates Using the History Column." To detect whether the row has been modified since the user queried it in the most efficient way, select the Change Indicator option to compare only the change-indicator attribute values.
If you need to keep track of historical information in your entity object, such as when an entity was created or modified and by whom, or the number of times the entity has been modified, you specify an attribute with the Track Change History option selected (on the Details tab).
If an attribute's data type is Number
, String
, or Date
, and if it is not part of the primary key, then you can enable this property to have your entity automatically maintain the attribute's value for historical auditing. How the framework handles the attribute depends which type of history attribute you indicate:
Created On: This attribute is populated with the time stamp of when the row was created. The time stamp is obtained from the database.
Created By: The attribute is populated with the name of the user who created the row. The user name is obtained using the getUserPrincipalName()
method on the Session
object.
Modified On: This attribute is populated with the time stamp whenever the row is updated/created.
Modified By: This attribute is populated with the name of the user who creates or updates the row.
Version Number: This attribute is populated with a long value that is incremented whenever a row is created or updated.
An entity object exhibits composition behavior when it creates (or composes) other entities, such as an OrderEO
entity creating a OrderItemEO
entity. This additional runtime behavior determines its role as a logical container of other nested entity object parts.
Note:
Composition also affects the order in which entities are validated. For more information, see Section 7.2.3, "Understanding the Impact of Composition on Validation Order."The features that are always enabled for composing entity objects are described in the following sections:
Section 4.10.13.1, "Orphan-Row Protection for New Composed Entities"
Section 4.10.13.2, "Ordering of Changes Saved to the Database"
Section 4.10.13.3, "Cascade Update of Composed Details from Refresh-On-Insert Primary Keys"
The additional features, and the properties that affect their behavior, are described in the following sections:
Section 4.10.13.5, "Cascade Update of Foreign Key Attributes When Primary Key Changes"
Section 4.10.13.7, "Updating of Composing Parent History Attributes"
When a composed entity object is created, it performs an existence check on the value of its foreign key attribute to ensure that it identifies an existing entity as its owning parent entity. At create time, if no foreign key is found or else a value that does not identify an existing entity object is found, the entity object throws an InvalidOwnerException
instead of allowing an orphaned child row to be created without a well-identified parent entity.
Note:
The existence check finds new pending entities in the current transaction, as well as existing ones in the database if necessary.Composition behavior ensures that the sequence of data manipulation language (DML) operations performed in a transaction involving both composing and composed entity objects is performed in the correct order. For example, an INSERT
statement for a new composing parent entity object will be performed before the DML operations related to any composed children.
When a new entity row having a primary key configured to refresh on insert is saved, then after its trigger-assigned primary value is retrieved, any composed entities will have their foreign key attribute values updated to reflect the new primary key value.
There are a number of additional composition related features that you can control through settings on the Association Properties page of the Create Association wizard or the overview editor. Figure 4-13 shows the Relationships page for the OrderItemsOrdersFkAssoc
association between two entity objects: OrderItemEO
and OrderEO
.
You can either enable or prevent the deletion of a composing parent while composed children entities exist. When the Implement Cascade Delete option (see Figure 4-13) is deselected, the removal of the composing entity object is prevented if it contains any composed children.
When selected, this option allows the composing entity object to be removed unconditionally together with any composed children entities. If the related Optimize for Database Cascade Delete option is deselected, then the composed entity objects perform their normal DELETE
statement at transaction commit time to make the changes permanent. If the option is selected, then the composed entities do not perform the DELETE
statement on the assumption that the database ON DELETE CASCADE
constraint will handle the deletion of the corresponding rows.
Select the Cascade Update Key Attributes option (see Figure 4-13) to enable the automatic update of the foreign key attribute values in composed entities when the primary key value of the composing entity is changed.
Select the Lock Top-Level Container option (see Figure 4-13) to control whether adding, removing, or modifying a composed detail entity row should attempt to lock the composing entity before allowing the changes to be saved.
Select the Update Top-Level History Columns option (see Figure 4-13) to control whether adding, removing, or modifying a composed detail entity object should update the Modified By and Modified On history attributes of the composing parent entity.
Sometimes a single database table stores information about several different kinds of logically related objects. For example, a payroll application might work with hourly, salaried, and contract employees all stored in a single EMPLOYEES
table with an EMPLOYEE_TYPE
column. In this case, the value of the EMPLOYEE_TYPE
column contains values like H
, S
, or C
to indicate respectively whether a given row represents an hourly, salaried, or contract employee. And while it is possible that many attributes and behavior are the same for all employees, certain properties and business logic may also depend on the type of employee.
In situations where common information exists across related objects, it may be convenient to represent these different types of entity objects using an inheritance hierarchy. For example, attributes and methods common to all employees can be part of a base Employee
entity object, while subtype entity objects like HourlyEmployee
, SalariedEmployee
, and ContractEmployee
extend the base Employee
object and add additional properties and behavior. The Discriminator attribute setting is used to indicate which attribute's value distinguishes the type of row. Section 4.19, "Using Inheritance in Your Business Domain Layer," explains how to set up and use inheritance.
Database primary keys are often generated from a sequence and may not be data you want to expose to the user for a variety of reasons. For this reason, it's often helpful to have alternate key values that are unique. For example, you might want to enforce that every customer have a unique email address. Because a customer may change their email address, you won't want to use that value as a primary key, but you still want the user to have a unique field they can use for login or other purposes.
Alternate keys are useful for direct row lookups via the findByKey
class of methods. Alternate keys are frequently used for efficient uniqueness checks in the middle tier. For information on how to find out if a value is unique, see Section 7.4.1, "How to Ensure That Key Values Are Unique."
To define an alternate key, you use the Create Entity Constraint wizard.
It may be helpful to have an understanding of how you set attribute properties. For more information, see Section 4.10, "Setting Attribute Properties."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To define alternate key values:
In the Application Navigator, right-click an entity object and choose New Entity Constraint.
Follow the steps in the Create Entity Constraint wizard to name your constraint and select the attribute or attributes that participate in the key.
On the Properties page, select Alternate Key and choose the appropriate Key Properties options.
For more information about the Key Properties options, press the F1 key or click Help.
When you define alternate key values, a hash map is created for fast access to entities that are already in memory.
In addition to having attributes that map to columns in an underlying table, your entity objects can include transient attributes that display values calculated (for example, using Java or Groovy) or that are value holders. For example, a transient attribute you create, such as FullName
, could be calculated based on the concatenated values of FirstName
and LastName
attributes.
Once you create the transient attribute, you can perform a calculation in the entity object Java class, or use a Groovy expression in the attribute definition to specify a default value.
If you want to be able to change the value at runtime, you can use a Groovy expression. If the calculated value is not likely to change (for example, if it's a sum of the line items), you can perform the calculation directly in the entity object Java class.
Use the Attributes page of the overview editor to create a transient attribute.
It may be helpful to have an understanding of the use of transient and calculated attributes. For more information, see Section 4.11, "Adding Transient and Calculated Attributes to an Entity Object."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To add a transient attribute to an entity object:
In the Application Navigator, double-click the entity object.
In the overview editor, click the Attributes navigation tab, and then click the New icon.
In the New Entity Attribute dialog, enter a name for the attribute and click OK.
On the Details tab (in the overview editor), set the Java attribute type and select the Transient option.
If the value will be calculated, set Updatable to Never.
When you add a transient attribute, JDeveloper updates the XML component definition for the entity object to reflect the new attribute.
The <Attribute>
tag of a transient attribute has no TableName
and a ColumnName
of $none$
, as shown in Example 4-10.
Example 4-10 XML Code for a Transient Attribute
<Attribute Name="FullName" IsUpdateable="false" IsQueriable="false" IsPersistent="false" ColumnName="$none$" Type="java.lang.String" ColumnType="$none$" SQLType="VARCHAR" > </Attribute>
In contrast, a persistent entity attribute has both a TableName
and a ColumnName
, as shown in Example 4-11.
When creating a transient attribute, you can use a Groovy expression to provide the default value.
It may be helpful to have an understanding of transient and calculated attributes. For more information, see Section 4.11, "Adding Transient and Calculated Attributes to an Entity Object."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create a transient attribute based on a Groovy expression:
In the Application Navigator, double-click the entity object.
In the overview editor, click the Attributes navigation tab, and then click the New icon.
In the New Entity Attribute dialog, enter a name for the attribute and click OK.
On the Details tab (in the overview editor), set the Java attribute type and select the Transient option.
If the value will be calculated, set Updatable to Never.
Select Expression for the default value type, and click the Edit button next to the value field.
Expressions that you define are evaluated using the Groovy scripting language, as described in Section 3.6, "Overview of Groovy Scripting Language Support." Groovy lets you insert expressions and variables into strings. The expression is saved as part of the entity object definition.
In the Edit Expression dialog, enter an expression in the field provided, as shown in Figure 4-14.
Attributes that you reference can include any attribute that the entity object defines. Do not reference attributes in the expression that are not defined by the entity object.
Select the appropriate recalculate setting.
If you select Always (default), the expression is evaluated each time any attribute in the row changes. If you select Never, the expression is evaluated only when the row is created.
You can optionally provide a condition for when to recalculate the expression.
For example, the following expression in the Based on the following expression field causes the attribute to be recalculated when either the Quantity
attribute or the UnitPrice
attribute are changed:
return (adf.object.isAttributeChanged("Quantity") || adf.object.isAttributeChanged("UnitPrice"));
You can also list attributes on which this attribute is dependent.
In Figure 4-14, the Quantity
and UnitPrice
attributes are selected, which causes the attribute to be recalculated when either attribute is changed.
Click OK to save the expression.
Then click OK to create the attribute.
Note:
If either the value expression or the optional recalculate expression that you define references an attribute from the base entity object, you must define this as a dependency on the Dependencies tab (on the Attributes page). On the Dependencies tab, locate the attributes in the Available list and shuttle each to the Selected list.When you base a transient attribute on a Groovy expression, a <TransientExpression>
tag is added to the entity object's XML file within the appropriate attribute, as shown in Example 4-12.
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, then 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 entity object, to make it a calculated attribute you need to:
Enable a custom entity object class on the Java page of the overview editor, choosing to generate accessor methods
Write Java code inside the accessor method for the transient attribute to return the calculated value
Specify each dependent attribute for the transient attribute on the Dependencies tab of the Attributes page
For example, after generating the view row class, the Java code to return the transient attribute's calculated value would reside in the getter method for the attribute (such as FullName
), as shown in Example 4-13.
Example 4-13 Getter Method for a Transient Attribute
// Getter method for FullName calculated attribute in UserImpl.java public String getFullName() { // Commented out original line since we'll always calculate the value // return (String)getAttributeInternal(FULLNAME); return getFirstName()+" "+getLastName(); }
To ensure that the transient attribute is reevaluated whenever the attributes to be concatenated (such as LastName
and FirstName
) might be changed by the end user, specify the dependent attributes for the transient attribute. On the Dependencies tab of the Attributes page, locate the attributes in the Available list and shuttle each to the Selected list.
Business events raised from the model layer are useful for launching business processes and triggering external systems synchronization by way of the Oracle Mediator.
Oracle Mediator supports declarative subscriptions which map business events to actions. In other words, you can define and publish a business event (such as a new customer being created) in one component, and then subscribe to that event in another component so that a business process is notified when it occurs. You can then, in the subscribing component, proceed with an action you assign to that event (such as sending a welcome new customer email).
You declaratively define business events at the entity level. You may also specify conditions under which those events should be raised. Business events that meet the specified criteria are raised upon successful commit of the changed data. A business event is raised to the Mediator on a successful create, update, or delete of an entity object.
To implement a business event, you perform the following tasks:
Create an event definition, as described in Section 4.12.4, "How to Create a Business Event."
Map the event definition to an event point and publish the event definition, as described in Section 4.12.7, "How to Publish a Business Event."
After the business event is published, you can subscribe to the event from another component, as described in Section 4.12.8, "How to Subscribe to Business Events."
An event definition describes an event that will be published and raised with an event system Mediator. An event definition is stored in an entity object's XML file with the elements shown in Table 4-2.
An event point is a place from which an event can be raised. On a successful commit, one of the event points shown in Table 4-3 can be raised to the Mediator for each entity in a transaction.
Table 4-3 Example Event Points Raised to the Mediator
DML Type | Event Name | Event Description |
---|---|---|
CREATE |
EntityCreated |
A new Entity has been created. |
UPDATE |
EntityUpdated |
An existing Entity has been updated. |
DELETE |
EntityDeleted |
An existing Entity has been deleted. |
Note that no events are raised by default; all events are custom. When you create the event, you can specify the name and DML operation appropriately.
For each event point, you must specify which event definitions should be raised on a particular event point. In other words, you must declaratively map each event definition to an event point.
Transactional event delivery, where event delivery is part of the transaction, is not supported by the framework.
Synchronous events, where the publisher waits for further processing until the subscriber has confirmed event reception, is not supported by the framework.
To create a business event, use the Business Events page of the overview editor.
It may be helpful to have an understanding of how business events work. For more information, see Section 4.12, "Creating Business Events."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
In the Application Navigator, double-click an entity object.
In the overview editor, click the Business Events navigation tab.
On the Business Events page, expand the Event Definitions section and click the New icon.
In the Create Business Event Definition dialog, provide a name that describes this event, such as EmployeeContactInfoChanged
.
In the payload table, click New and Delete to select the appropriate attributes for this event.
Alternatively, you can double-click the cell and pick the attributes you want.
Note:
Only attributes of supported types are displayed in the Entity Attribute column. WhileClobDomain
attributes are supported, very large clob data can impact performance.In the Value Sent field, choose whether the value should Always be sent, or Only if changed.
The Only if changed option provides the best performance because the attribute will be considered optional for the payload. If you leave the default Always, the payload will require the attribute whether or not the value has changed. For more details about payload efficiency, see Section 4.12.6, "What You May Need to Know About Payload."
Use the arrow buttons to rearrange the order of attributes.
The order that the attributes appear in defines their order in the generated XSD. Since you'll be using the XSD to build your Fabric mediator and BPEL process, you might want the most frequently accessed attributes at the top.
Click OK.
Repeat the procedure for each business event that you want to define. To publish an event, see Section 4.12.7, "How to Publish a Business Event."
When you create a business event, the entity object's XML file is updated with the event definition. Example 4-14 shows an example of the XML code for a business event. JDeveloper also generates an associated XSD file for the event schema that allows specification of required attributes and optional attributes. Required attributes correspond to Value Sent - Always in the Create Business Event Definition dialog, whereas optional attributes are those for which you changed Value Sent to Only if changed.
Example 4-14 XML Code for a Business Event
<EventDef Name="CustBusEvent1"> <Payload> <PayloadItem AttrName="Order.OrderId"/> <PayloadItem AttrName="LineItemId"/> <PayloadItem AttrName="ProductBase.ProductId" SendOnlyIfChanged="true"/> </Payload> </EventDef>
Example 4-15 shows an example of the XSD event schema for a business event.
Example 4-15 XSD Event Schema for a Business Event
<?xml version = '1.0' encoding = 'UTF-8'?> <xs:schema targetNamespace="/oracle/fodemo/storefront/entities/events/schema/OrderItemEO" xmlns="/oracle/fodemo/storefront/entities/events/schema/OrderItemEO" elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="CustBusEvent1Info"> <xs:complexType> <xs:sequence> <xs:element name="Order.OrderId" type="DecimalValuePair" minOccurs="1"/> <xs:element name="LineItemId" type="DecimalValuePair" minOccurs="1"/> <xs:element name="ProductBase.ProductId" type="DecimalValuePair" minOccurs="0"/> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="ValuePair" abstract="true"/> <xs:complexType name="DecimalValuePair"> <xs:complexContent> <xs:extension base="ValuePair"> <xs:sequence> <xs:element name="newValue" minOccurs="0"> <xs:complexType> <xs:complexContent> <xs:extension base="xs:anyType"> <xs:attribute name="value" type="xs:decimal"/> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> <xs:element name="oldValue" minOccurs="0"> <xs:complexType> <xs:complexContent> <xs:extension base="xs:anyType"> <xs:attribute name="value" type="xs:decimal"/> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:schema>
Example 4-16 shows an example of the EDL event definition for the entity object.
Example 4-16 EDL Event Definition for the Entity Object
<definitions targetNamespace="/oracle/fodemo/storefront/entities/events/edl/OrderItemEO" xmlns:ns0="/oracle/fodemo/storefront/entities/events/schema/OrderItemEO" xmlns="http://schemas.oracle.com/events/edl"> <schema-import namespace="/oracle/fodemo/storefront/entities/events/schema/OrderItemEO" location="OrderItemEO.xsd"/> <event-definition name="CustBusEvent1"> <content element="ns0:CustBusEvent1Info"/> </event-definition> </definitions>
The attributes of the associated entity object constitute the payload of a business event. The payload attributes for a business event are defined by the creator of the event. It isn't automatically optimized. When the event is defined, an attribute can be marked as sent Always or Only if changed. For events fired during creation, only new values are sent. For events fired during an update or delete, the new and old values are sent and only the attributes that should be based on the Value Sent setting. For best performance, you should include only the primary key attribute for delete events.
To support composition scenarios (such as a purchase order with line items), a child entity can raise events defined on the parent entity, and events defined on the child entity can include attributes from the parent entity. When a child entity raises an event on a parent entity, only a single event is raised for a particular top-level entity per transaction, regardless of how many times the child entity raises it.
In the case of entity subtypes (for example, a Staff
entity object is a subtype of the Persons
entity), ADF Business Components does not support the overriding of business events. Because the subscriber to a business event listens to the event using the event name, overriding of events could cause the event subscriber to receive payload data unintended for that subscriber. Therefore, this capability is not supported.
When defining business events, remember that while ClobDomain
attributes are supported, very large clob data can have performance implications.
To publish a business event, use the Business Events page of the entity objects overview editor.
It may be helpful to have an understanding of how business events are used in the application. For more information, see Section 4.12, "Creating Business Events."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You need to have already created the event definition, as described in Section 4.12.4, "How to Create a Business Event," before you can publish it.
In the Application Navigator, double-click an entity object.
In the overview editor, click the Business Events navigation tab.
On the Business Events page, expand the Event Publication section and click the Edit event publications icon.
In the Edit Event Publications dialog, click New to create a new event.
Double-click the new cell in Event column, and select the appropriate event.
Double-click the corresponding cell in Event Point column, and select the appropriate event point action.
You can optionally define conditions for raising the event using the Raise Conditions table.
Click OK.
After you have created a business event, you can subscribe and respond to the event.
It may be helpful to have an understanding of business events. For more information, see Section 4.12, "Creating Business Events."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You will also need to complete the following tasks:
Publish the business event, as described in Section 4.12.7, "How to Publish a Business Event."
Open the SCA project that will subscribe to the business event.
To subscribe to a business event:
Using the file system, copy the XSD and event definition files for the business event into your SCA project's source path.
In the Application Navigator, right-click the project, and choose New.
In the New Gallery, expand SOA Tier, select Service Components and then Mediator, and click OK.
In the Create Mediator dialog, select the Subscribe to Events template, as shown in Figure 4-15.
Click the Add icon to add an event.
In the Event Chooser dialog, click the Browse icon to navigate to and select the event's definition file, and then click OK.
In the Create Mediator dialog, you can optionally change the Consistency option and specify a Filter for the event.
Click OK to generate the mediator.
The resulting mediator (.mplan
file) is displayed in the overview editor.
You can now click the Add icon in the Routing Rules section to add a rule for how to respond to the event.
As described in this chapter, all of the database interaction and a large amount of declarative runtime functionality of an entity object can be achieved without using custom Java code. When you need to go beyond the declarative features to implement custom business logic for your entities, you'll need to enable custom Java generation for the entities that require custom code. Appendix D, "Most Commonly Used ADF Business Components Methods," provides a quick reference to the most common code that you will typically write, use, and override in your custom entity object and entity definition classes.
To enable the generation of custom Java classes for an entity object, use the Java page of the overview editor.
It may be helpful to have an understanding of custom Java classes. For more information, see Section 4.13, "Generating Custom Java Classes for an Entity Object."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To generate a custom Java class for an entity object:
In the Application Navigator, double-click the entity.
In the overview editor, click the Java navigation tab, and then click the Edit Java options icon.
In the Select Java Options dialog, select the types of Java classes you want to generate.
Entity Object Class — the most frequently customized, it represents each row in the underlying database table.
Entity Collection Class — rarely customized.
Entity Definition Class — less frequently customized, it represents the related class that manages entity rows and defines their structure.
Click OK.
When you select one or more custom Java classes to generate, JDeveloper creates the Java file(s) you've indicated. For example, assuming an entity object named fodemo.storefront.entities.OrderEO
, the default names for its custom Java files will be OrderEOImpl.java
for the entity object class and OrderEODefImpl.java
for the entity definition class. Both files are created in the same ./fodemo/storefront/entities
directory as the component's XML component definition file.
The Java generation options for the entity object continue to be reflected on subsequent visits to the Java page of the overview editor. Just as with the XML definition file, JDeveloper keeps the generated code in your custom Java classes up to date with any changes you make in the editor. If later you decide you didn't require a custom Java file for any reason, disabling the relevant options on the Java page causes the custom Java files to be removed.
When you enable the generation of a custom entity object class, if you also enable the Accessors option, then JDeveloper generates getter and setter methods for each attribute in the entity object. For example, an OrderEO
entity object that has the corresponding custom OrderEOImpl.java
class might have methods (like those shown in Example 4-17) generated in it.
Example 4-17 Getter and Setter Methods from OrderEOImpl.java
public DBSequence getOrderId() { ... } public void setOrderId(DBSequence value) { ... } public Date getOrderDate() { ... } public void setOrderDate(Date value) { ... } public String getOrderStatusCode() { ... } public void setOrderStatusCode(String value) { ... } public Number getCustomerId() { ... } public void setCustomerId(Number value) { ... } public String getShipToName() { ... } public void setShipToName(String value) { ... }
These methods allow you to work with the row data with compile-time checking of the correct data type usage. That is, instead of writing a line like this to get the value of the CustomerId
attribute:
Number customerId = (Number)order.getAttribute("CustomerId");
you can write the code like:
Number customerId = order.getCustomerId();
You can see that with the latter approach, the Java compiler would catch a typographical error had you accidentally typed CustomerCode
instead of CustomerId
:
// spelling name wrong gives compile error Number customerId = order.getCustomerCode();
Without the generated entity object accessor methods, an incorrect line of code like the following cannot be caught by the compiler:
// Both attribute name and type cast are wrong, but compiler cannot catch it String customerId = (String)order.getAttribute("CustomerCode");
It contains both an incorrectly spelled attribute name, as well as an incorrectly typed cast of the getAttribute()
return value. When you use the generic APIs on the Row
interface, which the base EntityImpl
class implements, errors of this kind raise exceptions at runtime instead of being caught at compile time.
As shown in Figure 4-16, when you've enabled generation of custom Java classes, they also appear as child nodes under the Application Sources node for the entity object. As with all ADF components, when you select an entity object in the Application Navigator, the Structure window provides a structural view of the entity. When you need to see or work with the source code for a custom Java file, there are two ways to open the file in the source editor:
You can right-click the Java file, and choose Open, as shown in Figure 4-16.
You can right-click an item in a node in the Structure window, and choose Go To Source.
The custom Java classes generated by JDeveloper extend the base classes for your entity object, and allow you the flexibility to implement custom code while maintaining the integrity of the generated code. The following sections provide additional information about custom Java classes.
When you use an XML-only entity object, at runtime its functionality is provided by the default ADF Business Components implementation classes. Each custom Java class that is generated extends the appropriate ADF Business Components base class so that your code inherits the default behavior and you can easily add to or customize it. An entity object class will extend EntityImpl
, while the entity definition class will extend EntityDefImpl
(both in the oracle.jbo.server
package).
Some developers are hesitant to add their own code to generated Java source files. Each custom Java source code file that JDeveloper creates and maintains for you includes the following comment at the top of the file to clarify that it is safe for you to add your own custom code to this file.
// --------------------------------------------------------------------- // --- File generated by Oracle ADF Business Components Design Time. // --- Custom code may be added to this class. // --- Warning: Do not modify method signatures of generated methods. // ---------------------------------------------------------------------
JDeveloper does not blindly regenerate the file when you click OK or Apply in an edit dialog. Instead, it performs a smart update to the methods that it needs to maintain, leaving your own custom code intact.
You can generate custom Java classes for your view objects when you need to customize their runtime behavior or when you simply prefer to have strongly typed access to bind variables or view row attributes.
To configure the default settings for ADF Business Components custom Java generation, you can choose Preferences from the Tools menu and open the Business Components page to set your preferences to be used for business components created in the future. Developers getting started with ADF Business Components should set their preference to generate no custom Java classes by default. As you run into a specific need for custom Java code, you can enable just the bit of custom Java you need for that one component. Over time, you'll discover which set of defaults works best for you.
The entity object is designed to function based on XML only or as an XML component definition combined with a custom Java class. To support this design choice, attribute values are not stored in private member fields of an entity's class (a file that is not present in the XML-only situation). Instead, in addition to a name, attributes are also assigned a numerical index in the entity's XML component definition based on the zero-based, sequential order of the <Attribute>
and association-related <AccessorAttribute>
tags in that file. At runtime, attribute values in an entity row are stored in a sparse array structure managed by the base EntityImpl
class, indexed by the attribute's numerical position in the entity's attribute list.
For the most part, this private implementation detail is unimportant, since as a developer using entity objects, you are shielded from having to understand this. However, when you enable a custom Java class for your entity object, this implementation detail relates to some of the generated code that JDeveloper maintains in your entity object class. It is sensible to understand what that code is used for. For example, in the custom Java class for a OrderEO
entity object, each attribute or accessor attribute has a corresponding generated integer enum. JDeveloper ensures that the values of these enums correctly reflect the ordering of the attributes in the XML component definition.
You'll also notice that the automatically maintained, strongly typed getter and setter methods in the entity object class use these attribute enums, as shown in Example 4-18.
Example 4-18 Getter and Setter Methods Using Attribute Constants in the Custom Entity Java Class
// In oracle.fodemo.storefront.entities.OrderEOImpl class public Date getOrderDate() { return (Date)getAttributeInternal(ORDERDATE); // <-- Attribute enum } public void setOrderDate(Date value) { setAttributeInternal(ORDERDATE, value); // <-- Attribute enum }
Another aspect of the maintained code related to entity attribute enums is the getAttrInvokeAccessor()
and setAttrInvokeAccessor()
methods. These methods optimize the performance of attribute access by numerical index, which is how generic code in the EntityImpl
base class typically accesses attribute values when performing generic processing. An example of the getAttrInvokeAccessor()
method is shown in Example 4-19. The companion setAttrInvokeAccessor()
method looks similar.
Example 4-19 getAttrInvokeAccessor() Method in the Custom Entity Java Class
// In oracle.fodemo.storefront.entities.OrderEOImpl class /** getAttrInvokeAccessor: generated method. Do not modify. */ protected Object getAttrInvokeAccessor(int index, AttributeDefImpl attrDef) throws Exception { if ((index >= AttributesEnum.firstIndex()) && (index < AttributesEnum.count())) { return AttributesEnum.staticValues()[index - AttributesEnum.firstIndex()].get(this); } return super.getAttrInvokeAccessor(index, attrDef); }
The rules of thumb to remember about this generated attribute index-related code are the following.
Add custom code if needed inside the strongly typed attribute getter and setter methods.
Use the overview editor to change the order or type of entity object attributes.
JDeveloper changes the Java signature of getter and setter methods, as well as the related XML component definition for you.
Don't modify the getAttrInvokeAccessor()
and setAttrInvokeAccessor()
methods.
Don't change the values of the attribute index numbers manually.
Note:
If you need to manually edit the generated attribute enums because of source control merge conflicts or other reasons, you must ensure that the zero-based ordering reflects the sequential ordering of the<Attribute>
and <AccessorAttribute>
tags in the corresponding entity object XML component definition.To better evaluate the difference between using custom generated entity classes and working with the generic EntityImpl
class, Example 4-20 shows a version of methods in a custom entity class (StoreFrontServiceImpl.java
) from a custom application module class (StoreFrontService2Impl.java
). Some important differences to notice are:
Attribute access is performed using strongly typed attribute accessors.
Association accessor attributes return the strongly typed entity class on the other side of the association.
Using the getDefinitionObject()
method in your custom entity class allows you to avoid working with fully qualified entity definition names as strings.
The createPrimaryKey()
method in your custom entity class simplifies creating the Key
object for an entity.
Example 4-20 Programmatic Entity Examples Using Strongly Typed Custom Entity Object Classes
package devguide.examples.appmodules; import oracle.fodemo.storefront.entities.OrderEOImpl; import oracle.fodemo.storefront.entities.PersonEOImpl; import oracle.fodemo.storefront.entities.ProductBaseEOImpl; import oracle.jbo.ApplicationModule; import oracle.jbo.JboException; import oracle.jbo.Key; import oracle.jbo.client.Configuration; import oracle.jbo.domain.DBSequence; import oracle.jbo.domain.Number; import oracle.jbo.server.ApplicationModuleImpl; import oracle.jbo.server.EntityDefImpl; // --------------------------------------------------------------------- // --- File generated by Oracle ADF Business Components Design Time. // --- Custom code may be added to this class. // --- Warning: Do not modify method signatures of generated methods. // --------------------------------------------------------------------- /** * This custom application module class illustrates the same * example methods as StoreFrontServiceImpl.java, except that here * we're using the strongly typed custom Entity Java classes * OrderEOImpl, PersonsEOImpl, and ProductsBaseEOImpl instead of working * with all the entity objects using the base EntityImpl class. */ public class StoreFrontService2Impl extends ApplicationModuleImpl { /**This is the default constructor (do not remove). */ public StoreFrontService2Impl() { } /* * Helper method to return an Order by Id */ private OrderEOImpl retrieveOrderById(long orderId) { EntityDefImpl orderDef = OrderEOImpl.getDefinitionObject(); Key orderKey = OrderEOImpl.createPrimaryKey(new DBSequence(orderId)); return (OrderEOImpl)orderDef.findByPrimaryKey(getDBTransaction(),orderKey); } /* * Find an Order by Id */ public String findOrderTotal(long orderId) { OrderEOImpl order = retrieveOrderById(orderId); if (order != null) { return order.getOrderTotal().toString(); } return null; } /* * Create a new Product and Return its new id */ public long createProduct(String name, String status, String shipCode) { EntityDefImpl productDef = ProductBaseEOImpl.getDefinitionObject(); ProductBaseEOImpl newProduct = (ProductBaseEOImpl)productDef.createInstance2(getDBTransaction(),null); newProduct.setProductName(name); newProduct.setProductStatus(status); newProduct.setShippingClassCode(shipCode); newProduct.setSupplierId(new Number(100)); newProduct.setListPrice(new Number(499)); newProduct.setMinPrice(new Number(479)); newProduct.setCreatedBy("Test Client"); newProduct.setLastUpdatedBy("Test Client"); newProduct.setCategoryId(new Number(5)); try { getDBTransaction().commit(); } catch (JboException ex) { getDBTransaction().rollback(); throw ex; } DBSequence newIdAssigned = newProduct.getProductId(); return newIdAssigned.getSequenceNumber().longValue(); } /* * Update the status of an existing order */ public void updateRequestStatus(long orderId, String newStatus) { OrderEOImpl order = retrieveOrderById(orderId); if (order != null) { order.setOrderStatusCode(newStatus); try { getDBTransaction().commit(); } catch (JboException ex) { getDBTransaction().rollback(); throw ex; } } } /* * Access an associated Customer entity from the Order entity */ public String findOrderCustomer(long orderId) { OrderEOImpl svcReq = retrieveOrderById(orderId); if (svcReq != null) { PersonEOImpl cust = (PersonEOImpl)svcReq.getPerson(); if (cust != null) { return cust.getFirstName() + " " + cust.getLastName(); } else { return "Unassigned"; } } else { return null; } } /* * Testing method */ public static void main(String[] args) { String amDef = "devguide.model.StoreFrontService"; String config = "StoreFrontServiceLocal"; ApplicationModule am = Configuration.createRootApplicationModule(amDef,config); /* * NOTE: This cast to use the StoreFrontServiceImpl class is OK since * this code is inside a business tier *Impl.java file and not in a * client class that is accessing the business tier from "outside". */ StoreFrontServiceImpl service = (StoreFrontServiceImpl)am; String total = service.findOrderTotal(1011); System.out.println("Status of Order # 1011 = " + total); String customerName = service.findOrderCustomer(1011); System.out.println("Customer for Order # 1011 = " + customerName); try { service.updateOrderStatus(1011,"CANCEL"); } catch (JboException ex) { System.out.println("ERROR: "+ex.getMessage()); } long id = 0; try { id = service.createProduct(null, "NEW", "CLASS1"); } catch (JboException ex) { System.out.println("ERROR: "+ex.getMessage()); } id = service.createProduct("Canon PowerShot G9", "NEW", "CLASS1"); System.out.println("New product created successfully with id = "+id); Configuration.releaseRootApplicationModule(am,true); } }
You may not always need or want UI-based or programmatic clients to work directly with entity objects. Sometimes, you may just want to use an external client program to access an application module and work directly with the view objects in its data model. Chapter 5, "Defining SQL Queries Using View Objects" describes how to easily combine the flexible SQL-querying of view objects with the business logic enforcement and automatic database interaction of entity objects to build powerful applications. The combination enables a fully updatable application module data model, designed to meet the needs of the current end-user tasks at hand, that shares the centralized business logic in your reusable domain business object layer.
However, it is important first to understand how view objects and entity objects can be used on their own before learning to harness their combined power. By learning about these objects in greater detail, you will have a better understanding of when you should use them alone and when to combine them in your own applications.
Since clients don't work directly with entity objects, any code you write that works programmatically with entity objects will typically be custom code in a custom application module class or in the custom class of another entity object.
To access an entity row, you use a related object called the entity definition. At runtime, each entity object has a corresponding entity definition object that describes the structure of the entity and manages the instances of the entity object it describes. After creating an application module and enabling a custom Java class for it, imagine you wanted to write a method to return a specific order. It might look like the retrieveOrderById()
method shown in Example 4-21.
It may be helpful to have an understanding of when to use a programmatic approach for working with entity objects and associations. For more information, see Section 4.14, "Working Programmatically with Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To find an entity object by primary key:
Find the entity definition.
You obtain the entity definition for an entity object (for example, OrderEO
) by passing its fully qualified name to the static getDefinitionObject()
method imported from the EntityDefImpl
class. The EntityDefImpl
class in the oracle.jbo.server
package implements the entity definition for each entity object.
Construct a key.
You build a Key
object containing the primary key attribute that you want to look up. For example, for the OrderEO
entity object you create a key containing the single orderId
value passed into the method as an argument.
Find the entity object using the key.
You use the entity definition's findByPrimaryKey()
method to find the entity object by key, passing in the current transaction object, which you can obtain from the application module using its getDBTransaction()
method. The concrete class that represents an entity object row is the oracle.jbo.server.EntityImpl
class.
Return the object or some of its data to the caller.
Example 4-21 show example code for a retrieveOrderById()
method developed using this basic procedure.
Example 4-21 Retrieving an OrderEO Entity Object by Key
/* Helper method to return an Order by Id */ private OrderEOImpl retrieveOrderById(long orderId) { EntityDefImpl orderDef = OrderEOImpl.getDefinitionObject(); Key orderKey = OrderEOImpl.createPrimaryKey(new DBSequence(orderId)); return (OrderEOImpl)orderDef.findByPrimaryKey(getDBTransaction(),orderKey); }
Note:
Theoracle.jbo.Key
object constructor can also take an Object array to support creating multiattribute keys, in addition to the more typical single-attribute value keys.You can create a method to access an associated entity based on an accessor attribute that requires no SQL code. For example, the method findOrderCustomer()
might find an order, then access the associated PersonEO
entity object representing the customer assigned to the order. For an explanation of how associations enable easy access from one entity object to another, see Section 4.3, "Creating and Configuring Associations."
To prevent a conflict with an existing method in the application module that finds the same associated entity using the same accessor attribute, you can refactor this functionality into a helper method that you can then reuse anywhere in the application module it is required. For example, the retrieveOrderById()
method (shown in Example 4-21) refactors the functionality that finds an order.
It may be helpful to have an understanding of when to use a programmatic approach for working with entity objects and associations. For more information, see Section 4.14, "Working Programmatically with Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To access an associated entity object using the accessor attribute:
Find the associated entity by the accessor attribute.
For example, the findOrderCustomer()
method uses the retrieveOrderById()
helper method to retrieve the OrderEO
entity object by ID.
Access the associated entity using the accessor attribute.
Using the attribute getter method, you can pass in the name of an association accessor and get back the entity object on the other side of the relationship. (Note that Section 4.3.3, "How to Change Entity Association Accessor Names," explains that renaming the association accessor allows it to have a more intuitive name.)
Return some of its data to the caller.
For example, the findOrderCustomer()
method uses the getter methods on the returned PersonEO
entity to return the assigned customer's name by concatenating their first and last names.
Notice that you did not need to write any SQL to access the related PersonEO
entity. The relationship information captured in the ADF association between the OrderEO
and PersonEO
entity objects is enough to allow the common task of data navigation to be automated.
Example 4-22 shows the code for findOrderCustomer()
that uses the helper method.
Example 4-22 Accessing an Associated Entity Using the Accessor Attribute
/* Access an associated Customer entity from the Order entity */ public String findOrderCustomer(long orderId) { //1. Find the OrderEO object OrderEOImpl order = retrieveOrderById(orderId); if (order != null) { //2. Access the PersonEO object using the association accessor attribute PersonEOImpl cust = (PersonEOImpl)order.getPerson(); if (cust != null) { //3. Return attribute values from the associated entity object return cust.getFirstName() + " " + cust.getLastName(); } else { return "Unassigned"; } } else { return null; } }
Once you've got an entity row in hand, it's simple to update it or remove it. You could add a method like the updateOrderStatus()
shown in Example 4-23 to handle the job.
It may be helpful to have an understanding of when to use a programmatic approach for working with entity objects and associations. For more information, see Section 4.14, "Working Programmatically with Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
Find the Order by ID.
Using the retrieveOrderById()
helper method, the updateOrderStatus()
method retrieves the OrderEO
entity object by Id.
Set one or more attributes to new values.
Using the EntityImpl
class' setAttribute()
method, the updateOrderStatus()
method updates the value of the Status
attribute to the new value passed in.
Commit the transaction.
Using the application module's getDBTransaction()
method, the updateOrderStatus()
method accesses the current transaction object and calls its commit()
method to commit the transaction.
Example 4-23 Updating an Existing Entity Row
/* Update the status of an existing order */ public void updateOrderStatus(long orderId, String newStatus) { //1. Find the order OrderEOImpl order = retrieveOrderById(orderId); if (order != null) { //2. Set its Status attribute to a new value order.setOrderStatusCode(newStatus); //3. Commit the transaction try { getDBTransaction().commit(); } catch (JboException ex) { getDBTransaction().rollback(); throw ex; } } }
The example for removing an entity row would be the same, except that after finding the existing entity, you would use the following line instead to remove the entity before committing the transaction:
// Remove the entity instead! order.remove();
In addition to using the entity definition to find existing entity rows, you can also use it to create new ones. In the case of product entities, you could write a createProduct()
method like the one shown in Example 4-24 to accept the name and description of a new product, and return the new product ID assigned to it. This example assumes that the ProductId
attribute of the ProductBaseEO
entity object has been updated to have the DBSequence
type (see Section 4.10.10, "How to Get Trigger-Assigned Primary Key Values from a Database Sequence"). This setting ensures that the attribute value is refreshed to reflect the value of the trigger from the corresponding database table, assigned to it from the table's sequence in the application schema.
It may be helpful to have an understanding of when to use a programmatic approach for working with entity objects and associations. For more information, see Section 4.14, "Working Programmatically with Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
Find the entity definition.
Using the getDefinitionObject()
method, the createProduct()
method finds the entity definition for the Product
entity.
Create a new instance.
Using the createInstance2()
method on the entity definition, the createProduct()
method creates a new instance of the entity object.
Note:
The method name has a2
at the end. The regular createInstance()
method has protected
access and is designed to be customized as described Section D.3.4, "EntityImpl Class" of Appendix D, "Most Commonly Used ADF Business Components Methods." The second argument of type AttributeList
is used to supply attribute values that must be supplied at create time; it is not used to initialize the values of all attributes found in the list. For example, when creating a new instance of a composed child entity row using this API, you must supply the value of a composing parent entity's foreign key attribute in the AttributeList
object passed as the second argument. Failure to do so results in an InvalidOwnerException
.Set attribute values.
Using the attribute setter methods on the entity object, the createProduct()
method assigns values for the Name
, Status
, and other attributes in the new entity row.
Commit the transaction.
Calling commit()
on the current transaction object, the createProduct()
method commits the transaction.
Return the trigger-assigned product ID to the caller.
Using the attribute getter method to retrieve the value of the ProductId
attribute as a DBSequence
, and then calling getSequenceNumber().longValue()
, the createProduct()
method returns the sequence number as a long
value to the caller.
Example 4-24 Creating a New Entity Row
/* Create a new Product and Return its new id */ public long createProduct(String name, String status, String shipCode) { //1. Find the entity definition for the Product entity EntityDefImpl productDef = ProductBaseEOImpl.getDefinitionObject(); //2. Create a new instance of a Product entity ProductBaseEOImpl newProduct = (ProductBaseEOImpl)productDef.createInstance2(getDBTransaction(),null); //3. Set attribute values newProduct.setProductName(name); newProduct.setProductStatus(status); newProduct.setShippingClassCode(shipCode); newProduct.setSupplierId(new Number(100)); newProduct.setListPrice(new Number(499)); newProduct.setMinPrice(new Number(479)); newProduct.setCreatedBy("Test Client"); newProduct.setLastUpdatedBy("Test Client"); newProduct.setCategoryId(new Number(5)); //4. Commit the transaction try { getDBTransaction().commit(); } catch (JboException ex) { getDBTransaction().rollback(); throw ex; } //5. Access the database-trigger-assigned ProductId value and return it DBSequence newIdAssigned = newProduct.getProductId(); return newIdAssigned.getSequenceNumber().longValue(); }
As an alternative to using a trigger-assigned value (as described in Section 4.10.10, "How to Get Trigger-Assigned Primary Key Values from a Database Sequence"), you can assign the value to a primary key when creating a new row using an Oracle sequence. This metadata-driven approach allows you to centralize the code to retrieve the primary key into a single Java file that can be reused by multiple entity objects.
Example 4-25 shows a simple CustomEntityImpl
framework extension class on which the entity objects are based. Its overridden create()
method tests for the presence of a custom attribute-level metadata property named SequenceName
and if detected, populates the attribute's default value from the next number in that sequence.
Example 4-25 CustomEntityImpl Framework Extension Class
package sample; import oracle.jbo.AttributeDef; import oracle.jbo.AttributeList; import oracle.jbo.server.EntityImpl; import oracle.jbo.server.SequenceImpl; public class CustomEntityImpl extends EntityImpl { protected void create(AttributeList attributeList) { super.create(attributeList); for (AttributeDef def : getEntityDef().getAttributeDefs()) { String sequenceName = (String)def.getProperty("SequenceName"); if (sequenceName != null) { SequenceImpl s = new SequenceImpl(sequenceName,getDBTransaction()); setAttribute(def.getIndex(),s.getSequenceNumber()); } } } }
It may be helpful to have an understanding of when to use a programmatic approach for working with entity objects and associations. For more information, see Section 4.14, "Working Programmatically with Entity Objects and Associations."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To assign the primary key value using an Oracle sequence:
Create the CustomEntityImpl.java
file in your project, and insert the code shown in Example 4-25.
In the Application Navigator, double-click the entity you want to edit.
In the overview editor, click the Attributes navigation tab, and select the attribute you want to edit.
Click the Details tab, and set the attribute Type to Number.
Click the Custom Properties tab, and click the Add icon.
Create a custom property with SequenceName
for the name, and the name of the database sequence for the value.
For example, for a Dept
entity, you could define the custom property SequenceName
on its Deptno
attribute with the value DEPT_TABLE_SEQ
.
For auditing purposes, once a row is added to a table, sometimes your requirements may demand that rows never be physically deleted from the table. Instead, when the end user deletes the row in the user interface, the value of a DELETED
column should be updated from "N
" to "Y
" to mark it as deleted. You can use two method overrides to alter an entity object's default behavior to achieve this effect.
To accomplish this, you need to perform the following tasks:
Update a deleted flag when a row is removed, as described in Section 4.14.6.1, "Updating a Deleted Flag When a Row Is Removed."
Force the entity object to be updated instead of deleted, as described in Section 4.14.6.2, "Forcing an Update DML Operation Instead of a Delete."
To update a deleted flag when a row is removed, enable a custom Java class for your entity object and override the remove()
method to set the deleted flag before calling the super.remove()
method. Example 4-26 shows what this would look like in the custom Java class of an entity object. It is important to set the attribute before calling super.remove()
since an attempt to set the attribute of a deleted row will encounter the DeadEntityAccessException
.
This example presumes that you've altered the table to have an additional DELETED
column, and synchronized the entity object with the database to add the corresponding Deleted
attribute.
Example 4-26 Updating a Deleted Flag When an Entity Row Is Removed
// In your custom Java entity class public void remove() { setDeleted("Y"); super.remove(); }
The row will still be removed from the row set, but it will have the value of its Deleted
flag modified to "Y
" in the entity cache. The second part of implementing this behavior involves forcing the entity to perform an UPDATE
instead of an DELETE
when it is asked to perform its DML operation. You need to implement both parts for a complete solution.
To force an entity object to be updated instead of deleted, override the doDML()
method and write code that conditionally changes the operation
flag. When the operation flag equals DML_DELETE
, your code will change it to DML_UPDATE
instead. Example 4-27 shows what this would look like in the custom Java class of an entity object.
This example presumes that you've altered the table to have an additional DELETED
column, and synchronized the entity object with the database to add the corresponding Deleted
attribute.
Example 4-27 Forcing an Update DML Operation Instead of a Delete
// In your custom Java entity class protected void doDML(int operation, TransactionEvent e) { if (operation == DML_DELETE) { operation = DML_UPDATE; } super.doDML(operation, e); }
With this overridden doDML()
method in place to complement the overridden remove()
method described in Section 4.14.6.1, any attempt to remove an entity through any view object with a corresponding entity usage will update the DELETED
column instead of physically deleting the row. Of course, in order to prevent "deleted" products from appearing in your view object query results, you will need to appropriately modify their WHERE
clauses to include only products WHERE DELETED = 'N'
.
Due to database constraints, when you perform DML operations to save changes to a number of related entity objects in the same transaction, the order in which the operations are performed can be significant. If you try to insert a new row containing foreign key references before inserting the row being referenced, the database can complain with a constraint violation. You must understand what the default order for the processing of entity objects is during commit time and how to programmatically influence that order when necessary.
Note:
The example in this section refers to theControllingPostingOrder
project of the AdvancedEntityExamples
application workspace in the StandaloneExamples
module of the Fusion Order Demo application.By default, when you commit the transaction the entity objects in the pending changes list are processed in chronological order, in other words, the order in which the entities were added to the list. This means that, for example, if you create a new Product
and then a new Supplier
related to that product, the new Product
will be inserted first and the new Supplier
second.
When two entity objects are related by a composition, the strict chronological ordering is modified automatically to ensure that composed parent and child entity rows are saved in an order that prevents violating any constraints. This means, for example, that a new parent entity row is inserted before any new composed children entity rows.
If your related entities are associated but not composed, then you need to write a bit of code to ensure that the related entities get saved in the appropriate order.
Consider the newProductForNewSupplier()
custom method from a PostModule
application module in Example 4-28. It accepts a set of parameters and:
Creates a new Product
.
Creates a new Supplier
.
Sets the product ID to which the server request pertains.
Commits the transaction.
Constructs a Result
Java bean to hold new product ID and supplier ID.
Returns the result.
Note:
The code makes the assumption that bothProducts.ProductId
and Suppliers.SupplierId
have been set to have DBSequence
data type to populate their primary keys based on a sequence.Example 4-28 Creating a New Product, Then a New Supplier, and Returning the New IDs
// In PostModuleImpl.java public Result newProductForNewSupplier(String supplierName, String supplierStatus, String productName, String productStatus, Number listPrice, Number minPrice, String shipCode) { oracle.jbo.domain.Date today = new Date(Date.getCurrentDate()); Number objectId = new Number(0); // 1. Create a new product ProductsBaseImpl newProduct = createNewProduct(); // 2. Create a new supplier SuppliersImpl newSupplier = createNewSupplier(); newSupplier.setSupplierName(supplierName); newSupplier.setSupplierStatus(supplierStatus); newSupplier.setCreatedBy("PostingModule"); newSupplier.setCreationDate(today); newSupplier.setLastUpdatedBy("PostingModule"); newSupplier.setLastUpdateDate(today); newSupplier.setObjectVersionId(objectId); // 3. Set the supplier id to which the product pertains newProduct.setSupplierId(newSupplier.getSupplierId().getSequenceNumber()); newProduct.setProductName(productName); newProduct.setProductStatus(productStatus); newProduct.setListPrice(listPrice); newProduct.setMinPrice(minPrice); newProduct.setShippingClassCode(shipCode); newProduct.setCreatedBy("PostingModule"); newProduct.setCreationDate(today); newProduct.setLastUpdatedBy("PostingModule"); newProduct.setLastUpdateDate(today); newProduct.setObjectVersionId(objectId); // 4. Commit the transaction getDBTransaction().commit(); // 5. Construct a bean to hold new supplier id and product id Result result = new Result(); result.setProductId(newProduct.getProductId().getSequenceNumber()); result.setSupplierId(newSupplier.getSupplierId().getSequenceNumber()); // 6. Return the result return result; } private ProductsBaseImpl createNewProduct(){ EntityDefImpl productDef = ProductsBaseImpl.getDefinitionObject(); return (ProductsBaseImpl) productDef.createInstance2(getDBTransaction(), null); }private SuppliersImpl createNewSupplier(){ EntityDefImpl supplierDef = SuppliersImpl.getDefinitionObject(); return (SuppliersImpl) supplierDef.createInstance2(getDBTransaction(), null); }
If you add this method to the application module's client interface and test it from a test client program, you get an error:
oracle.jbo.DMLConstraintException: JBO-26048: Constraint "PRODUCT_SUPPLIER_FK" violated during post operation: "Insert" using SQL Statement "BEGIN INSERT INTO PRODUCTS( SUPPLIER_NAME,SUPPLIER_STATUS,PRODUCT_NAME, PRODUCT_STATUS,LIST_PRICE,MIN_PRICE, SHIPPING_CLASS_CODE) VALUES (?,?,?,?,?,?,?) RETURNING PRODUCT_ID INTO ?; END;". ## Detail 0 ## java.sql.SQLException: ORA-02291: integrity constraint (FOD.PRODUCT_SUPPILER_FK) violated - parent key not found
When the PRODUCTS
row is inserted, the database complains that the value of its SUPPLIER_ID
foreign key doesn't correspond to any row in the SUPPLIERS
table. This occurred because:
The code created the Product
before the Supplier
Products
and Suppliers
entity objects are associated but not composed
The DML operations to save the new entity rows is done in chronological order, so the new Product
gets inserted before the new Supplier
.
To remedy the problem of attempting to add a product with a not-yet-valid supplier ID, you could reorder the lines of code in the example to create the Supplier
first, then the Product
. While this would address the immediate problem, it still leaves the chance that another application developer could create things in an incorrect order.
The better solution is to make the entity objects themselves handle the posting order so the posting will work correctly regardless of the order of creation. To do this, you need to override the postChanges()
method in the entity that contains the foreign key attribute referencing the associated entity object and write code as shown in Example 4-29. In this example, since it is the Product
that contains the foreign key to the Supplier
entity, you need to update the Product
to conditionally force a related, new Supplier
to post before the service request posts itself.
The code tests whether the entity being posted is in the STATUS_NEW
or STATUS_MODIFIED
state. If it is, it retrieves the related product using the getSupplier()
association accessor. If the related Supplier
also has a post-state of STATUS_NEW
, then first it calls postChanges()
on the related parent row before calling super.postChanges()
to perform its own DML.
Example 4-29 Overriding postChanges() in ProductsBaseImpl to Post Supplier First
// In ProductsBaseImpl.java public void postChanges(TransactionEvent e) { /* If current entity is new or modified */ if (getPostState() == STATUS_NEW || getPostState() == STATUS_MODIFIED) { /* Get the associated supplier for the product */ SuppliersImpl supplier = getSupplier(); /* If there is an associated supplier */ if (supplier != null) { /* And if its post-status is NEW */ if (supplier.getPostState() == STATUS_NEW) { /* * Post the supplier first, before posting this * entity by calling super below */ supplier.postChanges(e); } } } super.postChanges(e); }
If you were to re-run the example now, you would see that without changing the creation order in the newProductForNewSupplier()
method's code, entities now post in the correct order — first new Supplier
, then new Product
. Yet, there is still a problem. The constraint violation still appears, but now for a different reason.
If the primary key for the Suppliers
entity object were user-assigned, then the code in Example 4-29 would be all that is required to address the constraint violation by correcting the post ordering.
Note:
An alternative to the programmatic technique discussed here, which solves the problem at the Java EE application layer, is the use of deferrable constraints at the database layer. If you have control over your database schema, consider defining (or altering) your foreign key constraints to beDEFERRABLE INITIALLY DEFERRED
. This database setting causes the database to defer checking the constraint until transaction commit time. When this is done, the application can perform DML operations in any order, provided that by COMMIT
time all appropriate related rows have been saved and would alleviate the parent/child ordering. However, you would still need to write the code to cascade-update the foreign key values if the parent's primary key is assigned from a sequence, as described in Section 4.14.7.3.3, "Associations Based on DBSequence-Valued Primary Keys," and Section 4.14.7.3.4, "Refreshing References to DBSequence-Assigned Foreign Keys."In this example, however, the Suppliers.SupplierId
is assigned from a database sequence, and not user-assigned. So when a new Suppliers
entity row gets posted, its SupplierId
attribute is refreshed to reflect the database-assigned sequence value. The foreign key value in the Products.SupplierId
attribute referencing the new supplier is "orphaned" by this refreshing of the supplier's ID value. When the product's row is saved, its SUPPLIER_ID
value still doesn't match a row in the SUPPLIERS
table, and the constraint violation occurs again. The next two sections discuss the solution to address this "orphaning" problem.
Recall from Section 4.10.10, "How to Get Trigger-Assigned Primary Key Values from a Database Sequence," that when an entity object's primary key attribute is of DBSequence
type, during the transaction in which it is created, its numerical value is a unique, temporary negative number. If you create a number of associated entities in the same transaction, the relationships between them are based on this temporary negative key value. When the entity objects with DBSequence
-value primary keys are posted, their primary key is refreshed to reflect the correct database-assigned sequence number, leaving the associated entities that are still holding onto the temporary negative foreign key value "orphaned."
For entity objects based on a composition, when the parent entity object's DBSequence
-valued primary key is refreshed, the composed children entity rows automatically have their temporary negative foreign key value updated to reflect the owning parent's refreshed, database-assigned primary key. This means that for composed entities, the "orphaning" problem does not occur.
However, when entity objects are related by an association that is not a composition, you need to write a little code to insure that related entity rows referencing the temporary negative number get updated to have the refreshed, database-assigned primary key value. The next section outlines the code required.
When an entity like Suppliers
in this example has a DBSequence
-valued primary key, and it is referenced as a foreign key by other entities that are associated with (but not composed by) it, you need to override the postChanges()
method as shown in Example 4-30 to save a reference to the row set of entity rows that might be referencing this new Suppliers
row. If the status of the current Suppliers
row is New
, then the code assigns the RowSet
-valued return of the getProduct()
association accessor to the newProductsBeforePost
member field before calling super.postChanges()
.
Example 4-30 Saving Reference to Entity Rows Referencing This New Supplier
// In SuppliersImpl.java RowSet newProductsBeforePost = null; public void postChanges(TransactionEvent TransactionEvent) { /* Only bother to update references if Product is a NEW one */ if (getPostState() == STATUS_NEW) { /* * Get a rowset of products related * to this new supplier before calling super */ newProductsBeforePost = (RowSet)getProductsBase(); } super.postChanges(TransactionEvent); }
This saved RowSet
object is then used by the overridden refreshFKInNewContainees()
method shown in Example 4-31. It gets called to allow a new entity row to cascade-update its refreshed primary key value to any other entity rows that were referencing it before the call to postChanges()
. It iterates over the ProductsBaseImpl
rows in the newProductsBaseBeforePost
row set (if non-null) and sets the new supplier ID value of each one to the new sequence-assigned supplier value of the newly posted Suppliers
entity.
Example 4-31 Cascade-Updating Entity Rows with New SupplierId Value
// In SuppliersImpl.java protected void refreshFKInNewContainees() { if (newProductsBeforePost != null) { Number newSupplierId = getSupplierId().getSequenceNumber(); /* * Process the rowset of products that referenced * the new supplier prior to posting, and update their * SupplierId attribute to reflect the refreshed SupplierId value * that was assigned by a database sequence during posting. */ while (newProductsBeforePost.hasNext()) { ProductsBaseImpl svrReq = (ProductsBaseImpl)newProductsBeforePost.next(); product.setSupplierId(newSupplierId); } closeNewProductRowSet(); } }
After implementing this change, the code in Example 4-28 runs without encountering any database constraint violations.
This section describes several advanced techniques for working with associations between entity objects.
When you need to represent a more complex relationship between entities than one based only on the equality of matching attributes, you can modify the association's SQL clause to include more complex criteria. For example, sometimes the relationship between two entities depends on effective dates. A Product
may be related to a Supplier
, but if the name of the supplier changes over time, each row in the SUPPLIERS
table might include additional EFFECTIVE_FROM
and EFFECTIVE_UNTIL
columns that track the range of dates in which that product row is (or was) in use. The relationship between a Product
and the Supplier
with which it is associated might then be described by a combination of the matching SupplierId
attributes and a condition that the product's RequestDate
lie between the supplier's EffectiveFrom
and EffectiveUntil
dates.
You can set up this more complex relationship in the overview editor for the association. First, add any additional necessary attribute pairs on the Relationship page, which in this example would include one (EffectiveFrom
, RequestDate
) pair and one (EffectiveUntil
, RequestDate
) pair. Then, on the Query page you can edit the Where field to change the WHERE clause to be:
(:Bind_SupplierId = Product.SUPPLIER_ID) AND (Product.REQUEST_DATE BETWEEN :Bind_EffectiveFrom AND :Bind_EffectiveUntil)
For more information about creating associations, see Section 4.3, "Creating and Configuring Associations."
When you create a view link between two entity-based view objects, on the View Link Properties page, you have the option to expose view link accessor attributes both at the view object level as well as at the entity object level. By default, a view link accessor is exposed only at the view object level of the destination view object. By selecting the appropriate In Entity Object: SourceEntityName or In Entity Object:DestinationEntityName checkbox, you can opt to have JDeveloper include a view link attribute in either or both of the source or destination entity objects. This can provide a handy way for an entity object to access a set of related view rows, especially when the query to produce the rows depends only on attributes of the current row.
Each time you retrieve an entity association accessor row set, by default the entity object creates a new RowSet
object to allow you to work with the rows. This does not imply re-executing the query to produce the results each time, only creating a new instance of a RowSet
object with its default iterator reset to the "slot" before the first row. To force the row set to refresh its rows from the database, you can call its executeQuery()
method.
Since there is a small amount of overhead associated with creating the row set, if your code makes numerous calls to the same association accessor attributes, you can consider enabling the association accessor row set retention for the source entity object in the association. You can enable retention of the association accessor row set using the overview editor for the entity object that is the source for the association accessor. Select Retain Association Accessor Rowset in the Tuning section of the General page of the overview editor for the entity object.
Alternatively, you can enable a custom Java entity collection class for your entity object. As with other custom entity Java classes you've seen, you do this on the Select Java Options dialog that you open from the Java page of the overview editor for the entity object. In the dialog, select Generate Entity Collection Class. Then, in the YourEntityColl
Impl
class that JDeveloper creates for you, override the init()
method, and add a line after super.init()
that calls the setAssociationAccessorRetained()
method passing true
as the parameter. It affects all association accessor attributes for that entity object.
When this feature is enabled for an entity object, since the association accessor row set it not re-created each time, the current row of its default row set iterator is also retained as a side-effect. This means that your code will need to explicitly call the reset()
method on the row set you retrieve from the association accessor to reset the current row in its default row set iterator back to the "slot" before the first row.
Note, however, that with accessor retention enabled, your failure to call reset()
each time before you iterate through the rows in the accessor row set can result in a subtle, hard-to-detect error in your application. For example, say you iterate over the rows in an association accessor row set as shown in Example 4-32 to calculate some aggregate total.
Example 4-32 Iterating Over a Row Set Incorrectly
// In your custom Java entity classRowSet rs = (RowSet)getProducts(); while (rs.hasNext()) { ProductImpl r = (ProductImpl)rs.next(); // Do something important with attributes in each row }
The first time you work with the accessor row set, the code will work. However, since the row set (and its default row set iterator) are retained, the second and any subsequent time you access the row set, the current row will already be at the end of the row set and the while loop will be skipped because rs.hasNext()
will be false
. Instead, with this feature enabled, write your accessor iteration code as shown in Example 4-33.
Example 4-33 Iterating Over a Row Set and Resetting to the First Row
// In your custom Java entity class
RowSet rs = (RowSet)getProducts();
rs.reset(); // Reset default row set iterator to slot before first row!
while (rs.hasNext()) {
ProductImpl r = (ProductImpl)rs.next();
// Do something important with attributes in each row
}
When you find yourself repeating the same sanity-checking validations on the values of similar attributes across multiple entity objects, you can save yourself time and effort by creating your own data types that encapsulate this validation. For example, imagine that across your business domain layer there are numerous entity object attributes that store strings that represent email addresses. One technique you could use to ensure that end users always enter a valid email address everywhere one appears in your business domain layer is to:
Use a basic String
data type for each of these attributes
Add an attribute-level method validator with Java code that ensures that the String value has the format of a valid email address for each attribute
However, these approaches can become tedious quickly in a large application. Fortunately, ADF Business Components offers an alternative that allows you to create your own EmailAddress
data type that represents an email address. After centralizing all of the sanity-checking regarding email address values into this new custom data type, you can use the EmailAddress
as the type of every attribute in your application that represents an email address. By doing this, you make the intention of the attribute values more clear to other developers and simplify application maintenance by putting the validation in a single place. ADF Business Components calls these developer-created data types domains.
Domains are Java classes that extend the basic data types like String
, Number
, and Date
to add constructor-time validation to ensure the candidate value passes relevant sanity checks. They offer you a way to define custom data types with cross-cutting behavior such as basic data type validation, formatting, and custom metadata properties in a way that is inherited by any entity objects or view objects that use the domain as the Java type of any of their attributes.
Note:
The example in this section refers to theSimpleDomains
project in the AdvancedEntityExamples
application workspace in the StandaloneExamples
module of the Fusion Order Demo application.To create a domain, use the Create Domain wizard. This wizard is available from the New Gallery in the ADF Business Components category.
It may be helpful to have an understanding of using domains. For more information, see Section 4.15, "Creating Custom, Validated Data Types Using Domains."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
In the Application Navigator, right-click the project for which you want to create a domain and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Domain, and click OK.
In the Create Domain wizard, on the Name page, specify a name for the domain and a package in which it will reside. To create a domain based on a simple Java type, leave Domain for an Oracle Object Type unselected.
Click Next.
On the Settings page, indicate the base type for the domain and the database column type to which it will map.
For example, if you were creating a domain called ShortEmailAddress
to hold eight-character short email addresses, you would set the base type to String
and the Database Column Type to VARCHAR2(8)
. You can set other common attribute settings on this panel as well.
Click Finish to create your domain.
When you create a domain, JDeveloper creates its XML component definition in the subdirectory of your project's source path that corresponds to the package name you chose. For example, if you created the ShortEmailAddress
domain in the devguide.advanced.domains
package, JDeveloper would create the ShortEmailAddress.xml
file in the ./devguide/advanced/domains
subdirectory. A domain always has a corresponding Java class, which JDeveloper creates in the common
subpackage of the package where the domain resides. This means it would create the ShortEmailAddress.java
class in the devguide.advanced.domains.common
package. The domain's Java class is automatically generated with the appropriate code to behave in a way that is identical to one of the built-in data types.
Domains can be created as a variety of different types, and have different characteristics than standard attributes. The sections that follow describe some of the things you may need to know about when working with domains.
After you've created a domain in a project, it appears among the list of available data types in the Attribute Type dropdown list in the entity object and view object wizards and dialogs. To use the domain as the type of a given attribute, just pick it from the list.
Note:
The entity-mapped attributes in an entity-based view object inherit their data type from their corresponding underlying entity object attribute. Therefore, if the entity attribute uses a domain type, the matching view object attribute will as well. For transient or SQL-derived view object attributes, you can directly set the type to use a domain since it is not inherited from any underlying entity.Typically, the only coding task you need to do for a domain is to write custom code inside the generated validate()
method. Your implementation of the validate()
method should perform your sanity checks on the candidate value being constructed, and throw a DataCreationException
in the oracle.jbo
package if the validation fails.
In order to throw an exception message that is translatable, you can create a message bundle class similar to the one shown in Example 4-34. Create it in the same common
package as your domain classes themselves. The message bundle returns an array of {
MessageKeyString
,
TranslatableMessageString
}
pairs.
Example 4-34 Custom Message Bundle Class for Domain Exception Messages
package devguide.advanced.simpledomains.common; import java.util.ListResourceBundle; public class ErrorMessages extends ListResourceBundle { public static final String INVALID_SHORTEMAIL = "30002"; public static final String INVALID_EVENNUMBER = "30003"; private static final Object[][] sMessageStrings = new String[][] { { INVALID_SHORTEMAIL, "A valid short email address has no @-sign or dot."}, { INVALID_EVENNUMBER, "Number must be even."} }; /** * Return String Identifiers and corresponding Messages * in a two-dimensional array. */ protected Object[][] getContents() { return sMessageStrings; } }
Since String
is a base JDK type, a domain based on a String
aggregates a private mData String
member field to hold the value that the domain represents. Then, the class implements the DomainInterface
expected by the ADF runtime, as well as the Serializable
interface, so the domain can be used in method arguments or return types of the custom client interfaces of Oracle ADF components.
Example 4-35 shows the validate()
method for a simple ShortEmailAddress
domain class. It tests to make sure that the mData
value does not contains an at-sign or a dot, and if it does, then the method throws DataCreationException
referencing an appropriate message bundle and message key for the translatable error message.
Example 4-35 Simple ShortEmailAddress String-Based Domain Type with Custom Validation
public class ShortEmailAddress extends Object implements DomainInterface, Serializable { private String mData; // . . . /**Implements domain validation logic and throws a JboException on error. */ protected void validate() { int atpos = mData.indexOf('@'); int dotpos = mData.lastIndexOf('.'); if (atpos > -1 || dotpos > -1) { throw new DataCreationException(ErrorMessages.class, ErrorMessages.INVALID_SHORTEMAIL,null,null); } } // . . . }
Other simple domains based on a built-in type in the oracle.jbo.domain
package extend the base type, as shown in Example 4-36. It illustrates the validate()
method for a simple Number-based domain called EvenNumber
that represents even numbers.
Example 4-36 Simple EvenNumber Number-Based Domain Type with Custom Validation
public class EvenNumber extends Number { // . . . /** * Validates that value is an even number, or else * throws a DataCreationException with a custom * error message. */ protected void validate() { if (getValue() % 2 == 1) { throw new DataCreationException(ErrorMessages.class, ErrorMessages.INVALID_EVENNUMBER,null,null); } } // . . . }
When you create a simple domain based on one of the basic data types, it is an immutable class. That means that once you've constructed a new instance of it like this:
ShortEmailAddress email = new ShortEmailAddress("emailaddress1");
You cannot change its value. If you want to reference a different short email address, you just construct another one:
ShortEmailAddress email = new ShortEmailAddress("emailaddress2");
This is not a new concept because it's the same way that String
, Number
, and Date
classes behave, among others.
Oracle Database supports the ability to create user-defined types in the database. For example, you could create a type called POINT_TYPE
using the following DDL statement:
create type point_type as object ( x_coord number, y_coord number );
If you use user-defined types like POINT_TYPE
, you can create domains based on them, or you can reverse-engineer tables containing columns of object type to have JDeveloper create the domain for you. You can use the Create Domain wizard to create Oracle object type domains manually.
To manually create an Oracle object type domain:
In the Application Navigator, right-click the project for which you want to create a domain and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Domain, and click OK.
In the Create Domain wizard, on the Name page, select the Domain for an Oracle Object Type checkbox, then select the object type for which you want to create a domain from the Available Types list.
Click Next.
On the Settings page, use the Attribute dropdown list to switch between the multiple domain properties to adjust the settings as appropriate.
Click Finish to create the domain.
To reverse-engineer an Oracle object type domain:
In addition to manually creating object type domains, when you use the Business Components from Tables wizard and select a table containing columns of an Oracle object type, JDeveloper creates domains for those object types as part of the reverse-engineering process. For example, imagine you created a table like this with a column of type POINT_TYPE
:
create table interesting_points( id number primary key, coordinates point_type, description varchar2(20) );
If you create an entity object for the INTERESTING_POINTS
table in the Business Components from Tables wizard, then you get both an InterestingPoints
entity object and a PointType
domain. The latter is generated, based on the POINT_TYPE
object type, because it was required as the data type of the Coordinates
attribute of the InterestingPoints
entity object.
Unlike simple domains, object type domains are mutable. JDeveloper generates getter and setter methods into the domain class for each of the elements in the object type's structure. After changing any domain properties, when you set that domain as the value of a view object or entity object attribute, it is treated as a single unit. Oracle ADF does not track which domain properties have changed, only that a domain-valued attribute value has changed.
Note:
Domains based on Oracle object types are useful for working programmatically with data whose underlying type is an oracle object type. They can also simplify passing and receiving structure information to stored procedures. However, support for working with object type domains in the ADF binding layer is complete, so it's not straightforward to use object domain-valued attributes in declaratively databound user interfaces.After selecting a domain in the Application Navigator, you can quickly navigate to its implementation class using one of the following methods:
In the Application Navigator, right-click the domain and choose Go to Domain Class.
In the Structure window, double-click the domain class.
When you create a business components archive, as described in Section 38.3.1, "How to Package a Component into an ADF Library JAR," the domain classes and message bundle files in the *.common subdirectories of your project's source path get packaged into the *CSCommon.jar
. They are classes that are common to both the middle-tier application server and to an eventual remote client you might need to support.
You can define custom metadata properties on a domain. Any entity object or view object attribute based on that domain inherits those custom properties as if they had been defined on the attribute itself. If the entity object or view object attribute defines the same custom property, its setting takes precedence over the value inherited from the domain.
JDeveloper enforces the declarative settings you impose at the domain definition level: they cannot be made less restrictive for the entity object or view object for an attribute based on the domain type. For example, if you define a domain to have its Updatable property set to While New, then when you use your domain as the Java type of an entity object attribute, you can set Updatable to be Never (more restrictive) but you cannot set it to be Always. Similarly, if you define a domain to be Persistent, you cannot make it transient later. When sensible for your application, set declarative properties for a domain to be as lenient as possible, so you can later make them more restrictive as needed.
History types are used to track data specific to a point in time. JDeveloper ships with a number of history types, but you can also create your own. For more information on the standard history types and how to use them, see Section 4.10.12, "How to Track Created and Modified Dates Using the History Column."
You are not limited to the history types provided: you can add or remove custom history types using the History Types page in the Preferences dialog, and then write custom Java code to implement the desired behavior. The code to handle custom history types should be written in your application-wide entity base class for reuse.
Figure 4-17 shows a custom type called last update login
with type ID of 11
. Assume that last_update_login
is a foreign key in the FND_LOGINS
table.
It may be helpful to have an understanding of history types. For more information, see Section 4.16, "Creating New History Types."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create a custom history type:
From the Tools menu, choose Preferences.
In the Preferences dialog, expand ADF Business Components and click History Types.
On the History Types page, click New.
In the New History Type dialog, enter a string value for the name (spaces are allowed) and a numerical ID.
The Type Id must be an integer between 11 and 126. The numerical values 0-10 are reserved for internal use. The display string is displayed in the Track Change History dropdown list the next time you use the overview editor. Figure 4-18 shows the new history type in the Preferences dialog.
Open the EntityImpl.java
file and add a definition similar to the one in Example 4-37.
Override the getHistoryContextForAttribute(AttributeDefImpl attr)
method in the EntityImpl
base class with code similar to Example 4-38.
Example 4-38 Overriding getHistoryContextForAttribute()
@Override protected Object getHistoryContextForAttribute(AttributeDefImpl attr) { if (attr.getHistoryKind() == LASTUPDATELOGIN_HISTORY_TYPE) { // Custom History type logic goes here } else { return super.getHistoryContextForAttribute(attr); } }
Because they are typically used for auditing values over the life of an application, it is rare that you would want to remove a history type. However, in the event that you need to do so, perform the following tasks:
Remove the history type from the JDeveloper history types list in the Preferences dialog.
Remove any custom code you implemented to support the history type in the base EntityImpl.getHistoryContextForAttribute
method.
Remove all usages of the history type in the entity attribute metadata. Any attribute that you have defined to use this history type must be edited.
It may be helpful to have an understanding of history types. For more information, see Section 4.16, "Creating New History Types."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To remove a history type from the JDeveloper history types list:
From the Tools menu, choose Preferences.
In the Preferences dialog, expand ADF Business Components and click History Types.
On the History Types page, select the history type that you want to remove and click Delete.
If you have a PL/SQL package that encapsulates insert, update, and delete access to an underlying table, you can override the default DML processing event for the entity object that represents that table to invoke the procedures in your PL/SQL API instead. Often, such PL/SQL packages are used in combination with a companion database view. Client programs read data from the underlying table using the database view, and "write" data back to the table using the procedures in the PL/SQL package.
For example, say you want to create a Product
entity object based on such a combination of a view and a package.
Given the PRODUCTS
table in the Fusion Order Demo schema, consider a database view named PRODUCTS_V
, created using the following DDL statement:
create or replace view products_v as select product_id,name,image,description from products;
In addition, consider the simple PRODUCTS_API
package shown in Example 4-39 that encapsulates insert, update, and delete access to the underlying PRODUCTS
table.
Example 4-39 Simple PL/SQL Package API for the PRODUCTS Table
create or replace package products_api is procedure insert_product(p_prod_id number, p_name varchar2, p_supplier_id number, p_list_price number, p_min_price number, p_ship_code varchar2); procedure update_product(p_prod_id number, p_name varchar2, p_supplier_id number, p_list_price number, p_min_price number, p_ship_code varchar2); procedure delete_product(p_prod_id number); end products_api;
To create an entity object based on this combination of database view and PL/SQL package, you would perform the following tasks:
Create a view-based entity object, as described in Section 4.17.1, "How to Create an Entity Object Based on a View."
Create a base class for the entity object, as described in Section 4.17.3, "How to Centralize Details for PL/SQL-Based Entities into a Base Class."
Implement the appropriate stored procedure calls, as described in Section 4.17.4, "How to Implement the Stored Procedure Calls for DML Operations."
Handle selecting and locking functionality, if necessary, as described in Section 4.17.5, "How to Add Select and Lock Handling."
Note:
The example in these sections refers to theEntityWrappingPLSQLPackage
project of the AdvancedEntityExamples
application workspace in the StandaloneExamples
module of the Fusion Order Demo application.To create an entity object based on a view, you use the Create Entity Object wizard.
It may be helpful to have an understanding of how entity objects can use the PS/SQL API. For more information, see Section 4.17, "Basing an Entity Object on a PL/SQL Package API."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You will need to launch the Create Entity Object wizard as described in Section 4.2.2, "How to Create Single Entity Objects Using the Create Entity Wizard," and proceed through the wizard with the exceptions noted in the following procedure.
To create an entity object based on a view:
On the Name page, give the entity a name like Product
.
In the Select Schema Object dialog, select the Views checkbox in the Object Type section.
This enables the display of the available database views in the current schema in when you click Query.
Select the desired database view in the Available Objects list.
On the Attribute Settings page, use the Select Attribute dropdown list to choose the attribute that will act as the primary key, then select the Primary Key checkbox for that attribute.
Note:
When defining the entity based on a view, JDeveloper cannot automatically determine the primary key attribute since database views do not have related constraints in the database data dictionary.By default, an entity object based on a view performs all of the following directly against the underlying database view:
SELECT
statement (for findByPrimaryKey()
)
SELECT FOR UPDATE
statement (for lock()
), and
INSERT
, UPDATE
, DELETE
statements (for doDML()
)
To use stored procedure calls, you will need to override the doDML()
operations (as described in Section 4.17.3, "How to Centralize Details for PL/SQL-Based Entities into a Base Class"), and possibly override the lock()
and findByPrimaryKey()
handling (as described in Section 4.17.4, "How to Implement the Stored Procedure Calls for DML Operations").
If you plan to have more than one entity object based on a PL/SQL API, it's a smart idea to abstract the generic details into a base framework extension class. In doing this, you'll be using several of the concepts described in Chapter 12, "Extending Business Components Functionality." Start by creating a PLSQLEntityImpl
class that extends the base EntityImpl
class that each one of your PL/SQL-based entities can use as their base class. As shown in Example 4-40, you'll override the doDML()
method of the base class to invoke a different helper method based on the operation.
Note:
If you are already using an extended entity implementation class for your entity, you can extend it further with thePLSQLEntityImpl
class. For example, if you have a framework extension class named zzEntityImpl
, you would create a PLSQLEntityImpl
class that extends the zzEntityImpl
class.Example 4-40 Overriding doDML() to Call Different Procedures Based on the Operation
// In PLSQLEntityImpl.java protected void doDML(int operation, TransactionEvent e) { // super.doDML(operation, e); if (operation == DML_INSERT) callInsertProcedure(e); else if (operation == DML_UPDATE) callUpdateProcedure(e); else if (operation == DML_DELETE) callDeleteProcedure(e); }
In the PLSQLEntityImpl.java
base class, you can write the helper methods so that they perform the default processing like this:
// In PLSQLEntityImpl.java /* Override in a subclass to perform non-default processing */ protected void callInsertProcedure(TransactionEvent e) { super.doDML(DML_INSERT, e); } /* Override in a subclass to perform non-default processing */ protected void callUpdateProcedure(TransactionEvent e) { super.doDML(DML_UPDATE, e); } /* Override in a subclass to perform non-default processing */ protected void callDeleteProcedure(TransactionEvent e) { super.doDML(DML_DELETE, e); }
After putting this infrastructure in place, when you base an entity object on the PLSQLEntityImpl
class, you can use the Source > Override Methods menu item to override the callInsertProcedure()
, callUpdateProcedure()
, and callDeleteProcedure()
helper methods and perform the appropriate stored procedure calls for that particular entity.
Note:
If you do not override these helper methods in a subclass, they will perform the default processing as defined in the superclass. You only need to override the operations in thedoDML()
method that you want to provide alternative processing for.To simplify the task of implementing these calls, you could add the callStoredProcedure()
helper method (described in Section 12.6, "Invoking Stored Procedures and Functions") to the PLSQLEntityImpl
class as well. This way, any PL/SQL-based entity objects that extend this class can leverage the helper method.
To implement the stored procedure calls for DML operations, you will need to create a custom Java class for the entity object and override the operations in it.
It may be helpful to have an understanding of entity objects that are based on a PL/SQL package API. For more information, see Section 4.17, "Basing an Entity Object on a PL/SQL Package API."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To create the custom Java class with the override methods:
In the Application Navigator, double-click the entity object (for example, Products
).
In the overview editor, click the Java navigation tab.
On the Java page of the overview editor, click the Edit Java options icon.
In the Select Java Options dialog, click Classes Extend.
In the Override Base Classes dialog, in the Row field, enter the package and class of the PLSQLEntityImpl
class, or click Browse to search and select it.
Then select Generate Entity Object Class, and click OK.
In the Application Navigator, double-click the generated entity object class (for example, ProductsImpl.java
).
From the Source menu, choose Override Methods.
In the Override Methods dialog, select the callInsertProcedure()
, callUpdateProcedure()
, and callDeleteProcedure()
methods, and click OK.
Then enter the necessary code to override these procedures.
Example 4-41 shows some sample code that you would write in these overridden helper methods.
Example 4-41 Leveraging a Helper Method to Invoke Insert, Update, and Delete Procedures
// In ProductsImpl.java protected void callInsertProcedure(TransactionEvent e) { callStoredProcedure("products_api.insert_product(?,?,?,?,?,?)", new Object[] { getProductId(), getProductName(), getSupplierId(), getListPrice(), getMinPrice(), getShippingClassCode() }); } protected void callUpdateProcedure(TransactionEvent e) { callStoredProcedure("products_api.update_product(?,?,?,?,?,?)", new Object[] { getProductId(), getProductName(), getSupplierId(), getListPrice(), getMinPrice(), getShippingClassCode() }); } protected void callDeleteProcedure(TransactionEvent e) { callStoredProcedure("products_api.delete_product(?)", new Object[] { getProductId() }); }
At this point, if you create a default entity-based view object called Products
for the Products
entity object and add an instance of it to a ProductsModule
application module you can quickly test inserting, updating, and deleting rows from the Products
view object instance in the Oracle ADF Model Tester.
Often, overriding just the insert, update, and delete operations will be enough. The default behavior that performs the SELECT
statement for findByPrimaryKey()
and the SELECT FOR UPDATE
statement for the lock()
against the database view works for most basic kinds of views.
However, if the view is complex and does not support SELECT FOR UPDATE
or if you need to perform the findByPrimaryKey()
and lock()
functionality using additional stored procedures API's, then you can use the technique described in Section 4.17.5, "How to Add Select and Lock Handling."
You can handle the lock()
and findByPrimaryKey()
functionality of an entity object by invoking stored procedures if necessary. Imagine that the PRODUCTS_API
package were updated to contain the two additional procedures shown in Example 4-42. Both the lock_product
and select_product
procedures accept a primary key attribute as an IN
parameter and return values for the remaining attributes using OUT
parameters.
Example 4-42 Additional Locking and Select Procedures for the PRODUCTS Table
/* Added to PRODUCTS_API package */ procedure lock_product(p_prod_id number, p_name OUT varchar2, p_supplier_id OUT number, p_list_price OUT number, p_min_price OUT number, p_ship_code OUT varchar2); procedure select_product(p_prod_id number, p_name OUT varchar2, p_supplier_id OUT number, p_list_price OUT number, p_min_price OUT number, p_ship_code OUT varchar2);
To add select and lock handling, you will need to perform the following tasks:
Update the base class to handle lock and select, as described in Section 4.17.5.1, "Updating PLSQLEntityImpl Base Class to Handle Lock and Select."
Update the entity object implementation class to implement the lock and select behaviors, as described in Section 4.17.5.2, "Implementing Lock and Select for the Product Entity."
Override the lock()
method in the entity object implementation class to refresh the entity object after a RowInconsistentException
has occurred, as described in Section 4.17.5.3, "Refreshing the Entity Object After RowInconsistentException."
You can extend the PLSQLEntityImpl
base class to handle the lock()
and findByPrimaryKey()
overrides using helper methods similar to the ones you added for insert, update, delete. At runtime, both the lock()
and findByPrimaryKey()
operations invoke the lower-level entity object method called doSelect(boolean lock)
. The lock()
operation calls doSelect()
with a true
value for the parameter, while the findByPrimaryKey()
operation calls it passing false
instead.
Example 4-43 shows the overridden doSelect()
method in PLSQLEntityImpl
to delegate as appropriate to two helper methods that subclasses can override as necessary.
Example 4-43 Overriding doSelect() to Call Different Procedures Based on the Lock Parameter
// In PLSQLEntityImpl.java protected void doSelect(boolean lock) { if (lock) { callLockProcedureAndCheckForRowInconsistency(); } else { callSelectProcedure(); } }
The two helper methods are written to just perform the default functionality in the base PLSQLEntityImpl
class:
// In PLSQLEntityImpl.java /* Override in a subclass to perform non-default processing */ protected void callLockProcedureAndCheckForRowInconsistency() { super.doSelect(true); } /* Override in a subclass to perform non-default processing */ protected void callSelectProcedure() { super.doSelect(false); }
Notice that the helper method that performs locking has the name callLockProcedureAndCheckForRowInconsistency()
. This reminds you that you need to perform a check to detect at the time of locking the row whether the newly selected row values are the same as the ones the entity object in the entity cache believes are the current database values.
To assist subclasses in performing this old-value versus new-value attribute comparison, you can add one final helper method to the PLSQLEntityImpl
class like this:
// In PLSQLEntityImpl protected void compareOldAttrTo(int attrIndex, Object newVal) { if ((getPostedAttribute(attrIndex) == null && newVal != null) || (getPostedAttribute(attrIndex) != null && newVal == null) || (getPostedAttribute(attrIndex) != null && newVal != null && !getPostedAttribute(attrIndex).equals(newVal))) { throw new RowInconsistentException(getKey()); } }
With the additional infrastructure in place in the base PLSQLEntityImpl
class, you can override the callSelectProcedure()
and callLockProcedureAndCheckForRowInconsistency()
helper methods in the entity object implementation class (for example, ProductsImpl
). Because the select_product
and lock_product
procedures have OUT
arguments, as described in Section 12.6.4, "How to Call Other Types of Stored Procedures," you need to use a JDBC CallableStatement
object to perform these invocations.
Example 4-44 shows the code you would use to invoke the select_product
procedure for the ProductsImpl
entity object implementation class. It's performing the following basic steps:
Creating a CallableStatement
for the PLSQL block to invoke.
Registering the OUT
parameters and types, by one-based bind variable position.
Setting the IN
parameter value.
Executing the statement.
Retrieving the possibly updated column values.
Populating the possibly updated attribute values in the row.
Closing the statement.
Example 4-44 Invoking the Stored Procedure to Select a Row by Primary Key
// In ProductsImpl.java protected void callSelectProcedure() { String stmt = "begin products_api.select_product(?,?,?,?,?,?);end;"; // 1. Create a CallableStatement for the PLSQL block to invoke CallableStatement st = getDBTransaction().createCallableStatement(stmt, 0); try { // 2. Register the OUT parameters and types st.registerOutParameter(2, VARCHAR2); st.registerOutParameter(3, NUMBER); st.registerOutParameter(4, NUMBER); st.registerOutParameter(5, NUMBER); st.registerOutParameter(6, VARCHAR2); // 3. Set the IN parameter value st.setObject(1, getProductId()); // 4. Execute the statement st.executeUpdate(); // 5. Retrieve the possibly updated column values String possiblyUpdatedName = st.getString(2); String possiblyUpdatedSupplierId = st.getString(3); String possiblyUpdatedListPrice = st.getString(4); String possiblyUpdatedMinPrice = st.getString(5); String possiblyUpdatedShipCode = st.getString(6); // 6. Populate the possibly updated attribute values in the row populateAttribute(PRODUCTNAME, possiblyUpdatedName, true, false, false); populateAttribute(SUPPLIERID, possiblyUpdatedSupplierId, true, false, false); populateAttribute(LISTPRICE, possiblyUpdatedListPrice, true, false, false); populateAttribute(MINPRICE, possiblyUpdatedMinPrice, true, false, false); populateAttribute(SHIPPINGCLASSCODE, possiblyUpdatedShipCode, true, false, false); } catch (SQLException e) { throw new JboException(e); } finally { if (st != null) { try { // 7. Closing the statement st.close(); } catch (SQLException e) { } } } }
Example 4-45 shows the code to invoke the lock_product
procedure. It's doing basically the same steps as those in Example 4-44, with just the following two interesting differences:
After retrieving the possibly updated column values from the OUT
parameters, it uses the compareOldAttrTo()
helper method inherited from the PLSQLEntityImpl
to detect whether or not a RowInconsistentException
should be thrown as a result of the row lock attempt.
In the catch (SQLException e)
block, it is testing to see whether the database has thrown the error:
ORA-00054: resource busy and acquire with NOWAIT specified
and if so, it again throws the ADF Business Components AlreadyLockedException
just as the default entity object implementation of the lock()
functionality would do in this situation.
Example 4-45 Invoking the Stored Procedure to Lock a Row by Primary Key
// In ProductsImpl.java protected void callLockProcedureAndCheckForRowInconsistency() { String stmt = "begin products_api.lock_product(?,?,?,?,?,?);end;"; CallableStatement st = getDBTransaction().createCallableStatement(stmt, 0); try { st.registerOutParameter(2, VARCHAR2); st.registerOutParameter(3, NUMBER); st.registerOutParameter(4, NUMBER); st.registerOutParameter(5, NUMBER); st.registerOutParameter(6, VARCHAR2); st.setObject(1, getProductId()); st.executeUpdate(); String possiblyUpdatedName = st.getString(2); String possiblyUpdatedSupplierId = st.getString(3); String possiblyUpdatedListPrice = st.getString(4); String possiblyUpdatedMinPrice = st.getString(5); String possiblyUpdatedShipCode = st.getString(6); compareOldAttrTo(PRODUCTNAME, possiblyUpdatedName); compareOldAttrTo(SUPPLIERID, possiblyUpdatedSupplierId); compareOldAttrTo(LISTPRICE, possiblyUpdatedListPrice); compareOldAttrTo(MINPRICE, possiblyUpdatedMinPrice); compareOldAttrTo(SHIPPINGCLASSCODE, possiblyUpdatedShipCode); } catch (SQLException e) { if (Math.abs(e.getErrorCode()) == 54) { throw new AlreadyLockedException(e); } else { throw new JboException(e); } } finally { if (st != null) { try { st.close(); } catch (SQLException e) { } } } }
With these methods in place, you have and entity object that wraps the PL/SQL package (in this case, a Products
entity object with the PRODUCTS_API
package) for all of its database operations. Due to the clean separation of the data querying functionality of view objects and the data validation and saving functionality of entity objects, you can now leverage this entity object in any way you would use a normal entity object. You can build as many different view objects as necessary that use this entity object as their entity usage.
You can override the lock()
method to refresh the entity object after a RowInconsistentException
has occurred. Example 4-46 shows code that can be added to the entity object implementation class to catch the RowInconsistentException
and refresh the entity object.
If you need to create an entity object based on either of the following:
Synonym that resolves to a remote table over a DBLINK
View with INSTEAD OF
triggers
Then you will encounter the following error if any of its attributes are marked as Refresh on Insert or Refresh on Update:
JBO-26041: Failed to post data to database during "Update" ## Detail 0 ## ORA-22816: unsupported feature with RETURNING clause
These types of schema objects do not support the RETURNING
clause, which by default the entity object uses to more efficiently return the refreshed values in the same database roundtrip in which the INSERT
or UPDATE
operation was executed.
Because some types of schema objects do not support the RETURNING
clause, you might need to disable the RETURNING
clause in your entity object. The following procedures explains how to do that.
It may be helpful to have an understanding of the types of schema objects don't support the RETURNING
clause. For more information, see Section 4.18, "Basing an Entity Object on a Join View or Remote DBLink."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
To disable the use of the RETURNING clause for an entity object of this type:
Enable a custom entity definition class for the entity object.
In the custom entity definition class, override the createDef()
method to call setUseReturningClause(false)
.
If the Refresh on Insert attribute is the primary key of the entity object, you must specify some other attribute in the entity as an alternate unique key by setting the Unique Key property on it.
At runtime, when you have disabled the use of the RETURNING
clause as described in Section 4.18.1, "How to Disable the Use of the RETURNING Clause,", the entity object implements the Refresh on Insert and Refresh on Update behavior using a separate SELECT
statement to retrieve the values to refresh after insert or update as appropriate.
Inheritance is a powerful feature of object-oriented development that can simplify development and maintenance when used appropriately. As shown in Section 12.9, "Creating Extended Components Using Inheritance," ADF Business Components supports using inheritance to create new components that extend existing ones in order to add additional properties or behavior or modify the behavior of the parent component. Inheritance can be useful in modeling the different kinds of entities in your reusable business domain layer.
Note:
The example in this section refers to theInheritanceAndPolymorphicQueries
project of the AdvancedEntityExamples
application workspace in the StandaloneExamples
module of the Fusion Order Demo application.Your application's database schema might contain tables where different logical kinds of business information are stored in rows of the same table. These tables will typically have one column whose value determines the kind of information stored in each row. For example, the Fusion Order Demo application's PERSONS
table stores information about customers, suppliers, and staff in the same table. It contains a PERSON_TYPE_CODE
column whose value — STAFF
, CUST
, or SUPP
— determines what kind of PERSON
the row represents.
While the Fusion Order Demo implementation doesn't contain this differentiation, it's reasonable to assume that revisions of the application might require:
Managing additional database-backed attributes that are specific to suppliers or specific to staff
Implementing common behavior for all users that is different for suppliers or staff
Implementing new functionality that is specific to only suppliers or only staff
Figure 4-19 shows what the business domain layer would look like if you created distinct Persons
, Staff
, and Supplier
entity objects to allow distinguishing the different kinds of business information in a more formal way inside your application. Since suppliers and staff are special kinds of persons, their corresponding entity objects would extend the base Persons
entity object. This base Persons
entity object would contain all of the attributes and methods common to all types of users. The performPersonFunction()
method in the figure represents one of these common methods.
Then, for the Supplier
and Staff
entity objects you can add specific additional attributes and methods that are unique to that kind of user. For example, Supplier
has an additional ContractExpires
attribute of type Date
to track when the supplier's current contract expires. There is also a performSupplierFunction()
method that is specific to suppliers. Similarly, the Staff
entity object has an additional DiscountEligible
attribute to track whether the person qualifies for a staff discount. The performStaffFunction()
is a method that is specific to staff.
By modeling these different kinds of persons as distinct entity objects in an inheritance hierarchy in your domain business layer, you can simplify having them share common data and behavior and implement the aspects of the application that make them distinct.
To create entity objects in an inheritance hierarchy, you use the Create Entity Object wizard to create each entity.
The example described here assumes that you've altered the FOD application's PERSONS
table by executing the following DDL statement to add two new columns to it:
alter table persons add ( discount_eligible varchar2(1), contract_expires date );
To create entity objects in an inheritance hierarchy, you will perform the following tasks:
Identify the discriminator column and values, as described in Section 4.19.2.1, "Identifying the Discriminator Column and Distinct Values."
Identify the subset of attributes for each entity object, as described in Section 4.19.2.2, "Identifying the Subset of Attributes Relevant to Each Kind of Entity."
Create the base entity object, as described in Section 4.19.2.3, "Creating the Base Entity Object in an Inheritance Hierarchy."
Create the subtype entity objects, as described in Section 4.19.2.4, "Creating a Subtype Entity Object in an Inheritance Hierarchy."
Before creating entity objects in an inheritance hierarchy based on a table containing different kinds of information, you should first identify which column in the table is used to distinguish which kind of row it is.
For example, in the Fusion Order Demo application's PERSONS
table, this is the PERSON_TYPE_CODE
column. Since it helps partition or "discriminate" the rows in the table into separate groups, this column is known as the discriminator column.
Next, determine the valid values that the discriminator column takes on in your table. You might know this, or you could execute a simple SQL statement in the JDeveloper SQL Worksheet to determine the answer. To access the worksheet:
With the application open in JDeveloper, choose Database Navigator from the View menu.
Expand the workspace node, and select the connection.
In this example, expand the AdvancedEntityExamples node and select the FOD
connection.
Right-click the database connection, and choose Open SQL Worksheet.
In this example, the database connection is FOD.
Figure 4-20 shows the results of performing a SELECT DISTINCT
query in the SQL Worksheet on the PERSON_TYPE_CODE
column in the PERSONS
table. It confirms that the rows are partitioned into three groups based on the PERSON_TYPE_CODE
discriminator values: SUPP
, STAFF
, and CUST
.
Once you know how many different kinds of business entities are stored in the table, you will also know how many entity objects to create to model these distinct items. You'll typically create one entity object per kind of item. Then, to help determine which entity should act as the base of the hierarchy, you need to determine which subset of attributes is relevant to each kind of item.
For example, assume you determine that all of the attributes except ContractExpires
and DiscountEligible
are relevant to all users, and that:
ContractExpires
is specific to suppliers
DiscountEligible
is specific to staff.
This information leads you to determine that the Persons
entity object should be the base of the hierarchy, with the Supplier
and Staff
entity objects each extending Persons
to add their specific attributes.
To create the base entity object in an inheritance hierarchy, you use the Create Entity Object wizard.
It may be helpful to have an understanding of entity objects in an inheritance hierarchy. For more information, see Section 4.19, "Using Inheritance in Your Business Domain Layer."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You will also need to determine the discriminator column and values, as described in Section 4.19.2.1, "Identifying the Discriminator Column and Distinct Values," and determine the attributes for each entity object, as described in Section 4.19.2.2, "Identifying the Subset of Attributes Relevant to Each Kind of Entity."
To create the base entity object
In the Application Navigator, right-click the project you want to add the entity object to, and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Entity Object, and click OK.
In the Create Entity Object wizard, on the Name Page, provide a name and package for the entity, and select the schema object on which the entity will be based.
In this example, name the entity object Persons and base it on the PERSONS
table.
On the Attributes page, select the attributes in the Entity Attributes list that are not relevant to the base entity object (if any) and click Remove to remove them.
In this example, you would remove the DiscountEligible
and ContractExpires
attributes from the list.
On the Attribute Settings page, use the Select Attribute dropdown list to choose the attribute that will act as the discriminator for the family of inherited entity objects and select the Discriminator checkbox to identify it as such. Importantly, you must also supply a Default Value for this discriminator attribute to identify rows of this base entity type.
In this example, you would select the PersonTypeCode
attribute, mark it as a discriminator attribute, and set its Default Value to the value "cust
".
Note:
Leaving the Default Value blank for a discriminator attribute is legal. A blank default value means that a row with the discriminator column valueIS NULL
will be treated as this base entity type.Then click Finish to create the entity object.
To create a subtype entity object in an inheritance hierarchy, you use the Create Entity Object wizard.
It may be helpful to have an understanding of entity objects in an inheritance hierarchy. For more information, see Section 4.19, "Using Inheritance in Your Business Domain Layer."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You will also need to perform the following tasks:
Determine the discriminator column and values, as described in Section 4.19.2.1, "Identifying the Discriminator Column and Distinct Values."
Determine the attributes for each entity object, as described in Section 4.19.2.2, "Identifying the Subset of Attributes Relevant to Each Kind of Entity."
Create the parent entity object from which your new entity object will extend, as described in Section 4.19.2.3, "Creating the Base Entity Object in an Inheritance Hierarchy."
Make sure that the parent entity has a discriminator attribute already identified.
If it does not, use the overview editor to set the Discriminator property on the appropriate attribute of the parent entity before creating the inherited child.
To create the new subtype entity object in the hierarchy:
In the Application Navigator, right-click the project you want to add the entity object to, and choose New.
In the New Gallery, expand Business Tier, select ADF Business Components and then Entity Object, and click OK.
In the Create Entity Object wizard, on the Name Page, provide a name and package for the entity, and click the Browse button next to the Extends field to select the parent entity from which the entity being created will extend.
In this example, you would name the new entity Staff
and select the Persons
entity object in the Extends field.
On the Attributes page, the Entity Attributes list displays the attributes from the underlying table that are not included in the base entity object. Select the attributes you do not want to include in this entity object and click Remove.
In this example, because you are creating the Staff
entity you would remove the ContractExpires
attribute and leave the DiscountEligible
attribute.
Click Override to select the discriminator attribute so that you can customize the attribute metadata to supply a distinct Default Value for the Staff
subtype.
In this example, you would override the PersonTypeCode
attribute.
On the Attribute Settings page, use the Select Attribute dropdown list to select the discriminator attribute. Change the Default Value field to supply a distinct default value for the discriminator attribute that defines the entity subtype being created.
In this example, you would select the PersonTypeCode
attribute and change its Default Value to the value "staff
".
Click Finish to create the subtype entity object.
Note:
You can repeat the same steps to define theSupplier
entity object that extends Persons
to add the additional ContractExpires
attribute and overrides the Default Value of the UserRole
discriminator attribute to have the value "supp
".To add methods to entity objects in an inheritance hierarchy, enable the custom Java class for the entity object and use the source editor to add the method. Methods that are common to all entity objects in the hierarchy are added to the base entity, while subtype-specific methods are added to the subtype. You can also override methods from the base entity object in the subtypes as necessary.
To add a method that is common to all entity objects in the hierarchy, you add the method to the implementation class of the base entity object.
It may be helpful to have an understanding of entity objects in an inheritance hierarchy. For more information, see Section 4.19, "Using Inheritance in Your Business Domain Layer."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You will also need to create the base entity object and subtypes in the hierarchy, as described in Section 4.19.2, "How to Create Entity Objects in an Inheritance Hierarchy."
Too add a method common to all entity objects in a hierarchy:
In the Application Navigator, double-click the base entity object implementation class (for example, PersonsImpl.java
).
If the base entity object doesn't have a custom Java implementation class, you'll need to create it.
In the Application Navigator, double-click the entity object (for example, Persons
).
In the overview editor, click the Java navigation tab.
On the Java page of the overview editor, click the Edit Java options icon.
Then select Generate Entity Object Class, and click OK.
In the source editor, add the method.
For example, you could add the following method to the PersonsImpl
class for the base Persons
entity object:
// In PersonsImpl.java public void performPersonFunction() { System.out.println("## performPersonFunction as Customer"); }
Because this is the base entity object class, the methods you implement here are inherited by all subtype entity objects in the hierarchy.
To override a method in a subtype entity object that is common to all entity objects in the hierarchy, you modify the common method inherited from the base entity object in the implementation class of the subtype entity object.
It may be helpful to have an understanding of entity objects in an inheritance hierarchy. For more information, see Section 4.19, "Using Inheritance in Your Business Domain Layer."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You will also need to perform the following tasks:
Create the base entity object and subtypes in the hierarchy, as described in Section 4.19.2, "How to Create Entity Objects in an Inheritance Hierarchy."
Create the common method in the base entity object, which your subtype entity object will override, as described in Section 4.19.3.1, "Adding Methods Common to All Entity Objects in the Hierarchy."
Too override a method in a subtype entity object:
In the Application Navigator, double-click the subtype entity object implementation class (for example, StaffImpl.java
).
If the subtype entity object doesn't have a custom Java implementation class, you'll need to create it.
In the Application Navigator, double-click the entity object (for example, Staff
).
In the overview editor, click the Java navigation tab.
On the Java page of the overview editor, click the Edit Java options icon.
Then select Generate Entity Object Class, and click OK.
With the subtype entity object implementation class open in the source editor, choose Override Methods from the Source menu.
In the Override Methods dialog, select the method you want to override (for example, the performPersonFunction()
method), and click OK.
In the source editor, customize the overridden method's implementation.
For example, you could override the performPersonFunction()
method in the StaffImpl
class for the Staff
subtype entity object and change the implementation to look like this:
// In StaffImpl.java public void performPersonFunction() { System.out.println("## performPersonFunction as Staff"); }
When working with instances of entity objects in a subtype hierarchy, sometimes you will process instances of multiple different subtypes. Because the Staff
and Supplier
entity objects are special kinds of Persons
, you can write code that works with all of them using the more generic PersonsImpl
type that they all have in common. When doing this generic kind of processing of classes that might be one of a family of subtypes in a hierarchy, Java will always invoke the most specific override of a method available.
This means that invoking the performPersonFunction()
method on an instance of PersonsImpl
that happens to really be the more specific StaffImpl
subtype, will the result in printing out the following:
## performPersonFunction as Staff
instead of the default result that regular PersonsImpl
instances would get:
## performPersonFunction as Customer
To add a method that is specific to a subtype entity object in the hierarchy, you simply add the method in the implementation class of the subtype using the source editor.
It may be helpful to have an understanding of entity objects in an inheritance hierarchy. For more information, see Section 4.19, "Using Inheritance in Your Business Domain Layer."
You may also find it helpful to understand additional functionality that can be added using other entity object features. For more information, see Section 4.1.2, "Additional Functionality for Entity Objects."
You will also need to create the base entity object and subtypes in the hierarchy, as described in Section 4.19.2, "How to Create Entity Objects in an Inheritance Hierarchy."
Too add a method specific to a subtype entity object:
In the Application Navigator, double-click the subtype entity object implementation class (for example, SupplierImpl.java
).
If the base entity object doesn't have a custom Java implementation class, you'll need to create it.
In the Application Navigator, double-click the entity object (for example, Supplier
).
In the overview editor, click the Java navigation tab.
On the Java page of the overview editor, click the Edit Java options icon.
Then select Generate Entity Object Class, and click OK.
In the source editor, add the method.
For example, you could add a performSupplierFunction()
method to the SuppierImpl
class for the base Supplier
entity object:
// In SupplierImpl.java public void performSupplierFunction() { System.out.println("## performSupplierFunction called"); }
When using inheritance, you can also introduce a new base entity, find subtype entities using a primary key, and create view objects with polymorphic entity usages.
In the InheritanceAndPolymorphicQueries
example project, the Persons
entity object corresponds to a concrete kind of row in the PERSONS
table and it also played the role of the base entity in the hierarchy. In other words, all of its attributes were common to all entity objects in the hierarchy.
A situation might arise, however, where the Persons
entity object required a property that was specific to customers, but not common to staff or suppliers. Typically, for example, customers participate in customer satisfaction surveys, but staff and suppliers do not. The Persons
entity would require a LastSurveyDate
attribute to handle this requirement, but it wouldn't make sense to have Staff
and Supplier
entity objects inherit it.
In this case, you can introduce a new entity object (for example, BasePersons
) to act as the base entity in the hierarchy. It would have all of the attributes common to all Persons
, Staff
, and Supplier
entity objects. Then each of the three entities that correspond to concrete rows that appear in the table could have some attributes that are inherited from BasePersons
and some that are specific to the individual subtype. In the BasePersons
type, so long as you mark the PersonTypeCode
attribute as a discriminator attribute, you can just leave the Default Value blank (or some other value that does not occur in the PERSON_TYPE_CODE
column in the table). Because you will not use instances of the BasePersons
entity in the application, it doesn't matter what its discriminator default value is.
When you use the findByPrimaryKey()
method on an entity definition, it only searches the entity cache for the entity object type on which you call it. In the InheritanceAndPolymorphicQueries
example project, this means that if you call PersonsImpl.getDefinitionObject()
to access the entity definition for the Persons
entity object when you call findByPrimaryKey()
on it, you will only find entities in the cache that happen to be customers. Sometimes this is exactly the behavior you want.
However, if you want to find an entity object by primary key allowing the possibility that it might be a subtype in an inheritance hierarchy, then you can use the findByPKExtended()
methodfrom the EntityDefImpl
class instead.
For example, if you have created subtypes of the Persons
entity object, this alternative finder method would find an entity object by primary key whether it is a customer, supplier, or staff. You can then use the Java instanceof
operator to test which type you found, and then cast the PersonsImpl
object to the more specific entity object type to work with features specific to that subtype.
When you create an entity-based view object with an entity usage corresponding to a base entity object in an inheritance hierarchy, you can configure the view object to query rows corresponding to multiple different subtypes in the base entity's subtype hierarchy. Each row in the view object will use the appropriate subtype entity object as the entity row part, based on matching the value of the discriminator attribute. See Section 42.6.2, "How to Create a View Object with a Polymorphic Entity Usage," for specific instructions on setting up and using these view objects.