Skip Headers
Oracle® Fusion Applications Developer's Guide
11g Release 1 (11.1.2)

Part Number E15524-02
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
PDF · Mobi · ePub

57 Improving Performance

This chapter provides guidelines for you to write high-performing, highly scalable, and reliable applications on Oracle Fusion Middleware.

This chapter includes the following sections:

57.1 Introduction to Improving the Performance of Applications

The outcome of performance assessments of several prototypical Oracle Fusion Applications as well as various tests conducted by the Oracle Fusion Middleware performance team are captured in this chapter. It includes best practices for coding and tuning the Oracle Application Development Framework (ADF) Business Components-based applications with performance, scalability, and reliability (PSR) in mind. Other topics discussed include performance improvement guidelines for ADF ViewController layers and Oracle Fusion Middleware Extensions for applications.

This chapter assumes you are familiar with the concepts described in the Oracle Fusion Middleware Performance and Tuning Guide.

57.2 ADF Business Components Guidelines

To maximize performance while working with ADF Business Components, such as entity objects, view objects, application modules, and services, consider best practices. For more information about tuning Oracle ADF, see the "Oracle Application Development Framework Performance Tuning" chapter in the Oracle Fusion Middleware Performance and Tuning Guide.

57.2.1 Working with Entity Objects

When working with entity objects, consider the following suggestions for improving performance. For more information see the "What You May Need to Know About Optimizing View Object Runtime" in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.2.1.1 Enable Batch Updates for your Entity Objects

You can enable batch updates of your entity objects by selecting the Use Update Batching property on the Entity Object Editor - Tuning section as shown in Figure 57-1. You should also set the Threshold property to 1, which is important for _TL multi language entities.

Figure 57-1 Entity Object Editor — Tuning

Entity Object Editor - Tuning

When enabled, ADF Business Components combines multiple Data Manipulation Language (DML) operations and executes them in a single round trip. Modified rows are grouped into batches.

You should always enable batch updates, except in the following three cases where you should not:

  • You override the DML (PL/SQL entity objects are in this category).

  • You have one or more streaming attributes, such as character large object (CLOB) or binary large object (BLOB).

  • One or more attributes are marked as retrieve-on-insert or retrieve on-update.

For more information, see the "Batch Processing" section in the Oracle Fusion Middleware Performance and Tuning Guide.

57.2.1.2 Children Entity Objects in Composite Entity Associations Should not set the Foreign Key Attribute Values of the Parent

Children entity objects can expect that their parent primary key attribute values are passed through the attributeList parameter in create(attributeList) and ADF Business Components calls super.create(attributeList) to populate these foreign key attribute values. Repopulating the foreign key attribute values in the children entity object unnecessarily decreases performance.

57.2.1.3 Avoid Using List Validator Against Large Lists

When you use list validator, it scans the values in a linear fashion. Therefore, you should limit the list to not more than 20 to 30 values for frequently used list validators. Instead of using the list validator, you can use either an expression validator or a method validator. If this is a foreign key, then you can use a key exist validator.

57.2.1.4 Avoid Repeated Calls to the same Association Accessor

There is some cost to getting the parent or children via the association accessor. For example, if you are calling the same association accessor on the same entity object in a loop, then you should move the call to outside the loop.

57.2.1.5 Close Unused RowSets

There are various places in ADF Business Components that loop through all rowsets of a view object. You should call RowSet.closeRowSet on any rowsets that you no longer need. The typical case where you would have opened a rowset is when you get the "many" end of an association, for example, when retrieving Emp from Dept.

By default, the rowset is cleared when a garbage collection occurs. However, if you can close the rowset as soon as you finish using it, it improves performance and reduces the amount of work done during garbage collection. To close a rowset, call RowSet.closeRowSet. You should close it only if you know you no longer need it, and would not make calls such as previous(). If you are getting a row iterator from an association accessor, you can cast it to a RowSet and call closeRowSet on it.

Caution:

If you use the Retain Association Accessor RowSet option, then you should not call closeRowSet.

57.2.1.6 Use "Retain Association Accessor RowSet" when Appropriate

By default, the entity object creates a new RowSet object each time you retrieve an entity association accessor rowset, to allow you to work with the rows. However, creating a new RowSet object does not imply re-executing the query to produce the results each time, since only a new instance of a RowSet object is created, with its default iterator reset to the "slot" before the first row. There is some overhead to creating all these new rowsets even though the ones not in use are cleared on Java Virtual Machine (JVM) garbage collections. You may also see additional query executions due to the rowsets (and hence the underlying query collections) not being retained.

For high-traffic entity objects, such as those used for bulk loading, where the same association accessor is called many times, consider using the Retain Association Accessor Rowset option to improve performance. Typically, an association accessor would be used multiple times if:

  • Your entity object is Multi-Language Support (MLS)-enabled and has more than one translated attribute.

  • You have defaulting logic or multiple validators that need to access the same association attribute.

Using the Retain Association Accessor RowSet option may adversely affect memory usage since it postpones when the retained rowset becomes garbage collectible. Before you enable this option, (as shown in Figure 57-2), you should profile your flow to make sure you would indeed get a noticeable benefit.

Figure 57-2 Entity Object Editor — Tuning: Retain Association Accessor RowSet

EO Editor - Tuning: Retain Ass. Accessor RowSet

If you see the top CPU consumers (sort by exclusive CPU) are related to code that loops through the rowsets, then you would likely get a benefit by using this option. An example where you may want to consider using the Retain Association Accessor RowSet option is if you profile your code and see that oracle.jbo.server.ViewObjectImpl.addRowSet is using a lot of CPU, and most of the CPU is in a call stack that includes AssociationDefImpl.get. Figure 57-3 illustrates an example profiler output showing addRowSet being expensive.

Figure 57-3 Profiler Output Example

Profiler Output Example

Before you decide to retain association accessor, you should try the guideline Section 57.2.1.5, "Close Unused RowSets."

If you decide to use the Retain Association Accessor RowSet option, you should be aware of the potential behavior changes. For more information, see the Advanced Entity Association Techniques section in the "Advanced Entity Object Techniques" chapter of the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.2.1.7 Mark the Change Indicator Column

If your table has an OBJECT_VERSION_NUMBER column, make sure you check the Change Indicator attribute property. Columns marked as Change Indicator are automatically in any view object that includes that particular entity object.

57.2.2 Working with View Objects

When working with view objects, consider the following suggestions for improving performance.

57.2.2.1 Tune the View Object SQL Statement

You should tune both the list of attributes included in the view object as well as the underlying SQL statement. Avoid using the "one-size fits all" view objects which include many other attributes that are not needed for your usage. These additional attributes consume unnecessary memory.

You should capture the SQLs the view object is generating, with relevant view criteria applied, by enabling Java Business Objects (JBO) debug logging. (For expert-mode view objects, you should capture the SQL that you are providing). You should also generate explain plans against a volume database to ensure performance is optimal and the correct indexes are in place.

If you must use hints to get a desirable execution plan for your query, set the Query Optimizer Hint field in the View Object Editor - Tuning section as shown in Figure 57-4.

Figure 57-4 View Object Editor — Tuning

View Object Editor - Tuning

For user interface (UI) driven queries, the FIRST_ROWS(10) hint should be used to instruct the optimizer to pick a plan that is optimized to return the first set of rows. You should set this hint for view objects that are used for UI components, which typically just displays the initial set of rows (such as table). If you are fetching all the rows, then do not use the FIRST_ROWS hint.

57.2.2.2 Select the Correct Usage for View Objects

To maximize view object performance, the view object should match the intended usage. For more information about correct usage for view objects, see the "Creating View Objects" section in the Oracle Fusion Middleware Performance and Tuning Guide.

57.2.2.3 Set Appropriate Fetch Size and Max Fetch Size

How the view object is configured to fetch data plays a large role in the view object performance. For more information about tuning the fetch options for the application, see the "Configuring View Object Data Fetching" section in the Oracle Fusion Middleware Performance and Tuning Guide.

Due to the memory requirements for large batch size, we do not recommend using a fetch size of over 100. For view objects used on UIs, fetch size should not exceed 30.

Caution:

Java Database Connectivity (JDBC) pre-allocates memory to hold return data based on fetch size, so the practice of applying a fixed fetch size, such as 30, to all view objects should be avoided.

If you have a view object that is used in both query and insert, then you should call setMaxFetchSize(0) programmatically when you know it is being used in insert mode. In this case, you need to unset it when using it in query mode. You cannot set the No Rows option because the same view object is used in both insert and query mode in the same application module.

For view objects used for the List of Values (LOV) combo box, the number of rows fetched by Oracle ADF is controlled by the ListRangeSize setting in the LOV definition. The default value is 10 and a fetch size of 11 is appropriate. You should modify the value to 11.

For LOV text output, Oracle ADF fetches about 31 rows in the LOV search results window. To simplify retrieval, a fetch size of 11 is acceptable, to make it the same fetch size as the view object used in the LOV combo box. In this case, the data comes back in three round-trips which is also acceptable.

Note:

ADF Business Components only recognizes fetch size if the SQL flavor is Oracle, which is what you should be using.

Fetch size can be set based on the usage of the view object. This is the appropriate place to set the fetch size for view objects that are used in different scenarios and the fetch size cannot be pre-determined when the view object is created. You can edit the setting per view object usage by selecting the view object in the Data Model panel of the Application Module editor, clicking Edit, and then selecting the Tuning panel.

Fetch size can also be set at the view accessor level. This should be used by teams consuming public view objects from other teams, such as for LOVs. The producer team would likely not set a fetch size since they cannot anticipate how their public view object would be used. For more information, see the "Working with List of Values (LOV) in View Object Attributes" section in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.2.2.4 Use Bind Variables

Always use bind variables when setting the WHERE clause or when defining view criteria, as this allows the SQLs to be shared. However, there are some limited cases where you cannot use bind variables, such as when you need to use histograms. For more information, see the "Additional View Object Configurations" section in the Oracle Fusion Middleware Performance and Tuning Guide.and the "Working with Bind Variables" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.2.2.5 Include at Least One Required or Selectively Required View Criteria Item

When creating view criteria, include at least one view criteria item that is required or selectively required, in order to use a database index and avoid a full table scan. Otherwise, the SQL generated will be of the form:

((MyTableColumn_name = :bvOrgId) OR (:bvOrgId IS NULL))

In this example, the query cannot be derived from an index on MyTable.Column_name due to the presence of the :bvOrgId IS NULL condition.

Note:

The :bind IS NULL condition is generated only if the View Criteria Item (VCI) is against a bind variable and the Ignore Null Values option is selected.

57.2.2.6 Use Forward-Only Mode when Possible

If a dataset is only traversed going forward, then forward-only mode can help performance when iterating through the dataset. For more information, see the "Configuring View Object Data Fetching" section in the Oracle Fusion Middleware Performance and Tuning Guide.

The setForwardOnly API is actually defined on the RowSet interface, which ViewObjectImpl implements, so you can use it on secondary rowsets that you create via ViewObjectImpl.createRowSet(String name) as well.

57.2.2.7 Avoid Calling getRowCount

Calling getRowCount on a view object results in all rows being fetched into memory. Unless you intend to actually fetch all the rows, this call should be avoided. Use a combination of vo.hasNext,hasPrevious,getCurrent, or vo.getFetchedRowCount,if the row set has been executed and you are attempting to see if there is at least 1 row fetched.

If you really need to find out how many rows are in the result set, and you know the result set is likely going to contain more than 50 rows, you should use getEstimatedRowCount. This triggers a count query but does not fetch all of the matching rows into memory.

There is also a method on the view object, getCappedRowCount(n), which executes a query and a count up to n number of rows. If the row count is less than n, it returns a positive number, and it returns a negative number if row count is more than n.

57.2.2.8 Avoid Entity Object Fault-in by Selecting Necessary Attributes Up-Front

If your view object is based on entity objects, and you request an attribute that is not fetched in the initial view object query, ADF Business Components must execute a "fault-in" SQL to fetch the entire entity object. This is expensive and can be avoided by initially selecting the list of attributes you are fetching in a view object. For example, if you know your validation logic accesses an attribute that is not displayed in the UI, you should fetch it in the initial view object query.

By default, only the key attributes are selected when executing a Declarative view object programmatically. A "fault-in" query is executed to get the rest of the attributes if they are referenced. To avoid this, you should use the following ViewObjectImpl methods to specify the columns that need to be selected when executing a Declarative view object programmatically: resetSelectedAttributeDefs, selectAttributeDefs, and unselectAttributeDefs.

57.2.2.9 Reduce the Number of View Object Key Attributes to a Minimum

If your view object is based on multiple entity objects, restrict the number of Key Attributes to a minimal set that uniquely identifies the row.

Note:

By default, the primary key of the view object is the concatenation of the primary key of all the underlying entity objects, which typically will be a lot more columns than what is actually needed to uniquely identify a row.

For those attributes that do not need to be part of the key, deselect the Key Attribute option in the View Object Attribute Editor.

57.2.2.10 Use Range Paging when Jumping to Different Row Ranges

View objects provide a mechanism to page through large datasets giving users the ability to jump to a specific page in the results. To implement this feature, select Range Paging Incremental from the Access Mode dropdown list in the View Object Editor - Tuning section as shown in Figure 57-5.

Figure 57-5 View Object Editor — Tuning: Access Mode

VO Editor - Tuning: Access Mode

For more information, see the "Optimize large data sets" row in the "Additional View Object Configurations" table in the Oracle Fusion Middleware Performance and Tuning Guide.

57.2.2.11 Use setListenToEntityEvents(false) for Non-UI Scenarios

The setListenToEntityEvents(false) method instructs the view object not to listen to entity events and therefore, the view object and all its row sets does not receive events generated from changes to entity row data. This is useful for batch processing because suppressing events improves performance.

Note:

These events are not related to the business events that you may have defined in the entity object.

When you call an association accessor, an internal view object is created. If you insert or update via the association accessor, you can call setListenToEntityEvents(false) for the internal view object by casting it to a RowSet as shown in Example 57-1.

Example 57-1 Use setListenToEntityEvents(false)

RowSet myRowSet = (RowSet) myEntityImpl.getAttribute("<Accessor Name>");
((ViewObjectImpl) MyRowSet.getViewObject()).setListenToEntityEvents(false);

57.2.2.12 Use Appropriate Getter or Setter on View Row

If you have a ViewRowImpl class generated for your view object, you should call the named getter or setter if possible. For example, getEmployeeName or setEmployeeName, rather than the generic getAttribute or setAttribute.

Note:

Performance alone is not a sufficient reason for creating a custom ViewRowImpl class.

If you must use the generic getAttribute or setAttribute, consider using the index instead of the name for a small performance gain. It may be more troublesome to maintain the numeric attribute indexes, but for cases where you are looping through a large number of attributes, you should consider using getAttribute(int index) and setAttribute(int index).

57.2.2.13 Use Appropriate Indexes with Case-Insensitive View Criteria Items

A view criteria item on a varchar2 column is, by default, marked as case-insensitive. The generated predicate is in the form of UPPER (column_name) operator UPPER (:bindVariable). Since the left-hand side is UPPER(column_name), the existing non-function-based indexes created based on column_name is of no use for this kind of clause, and as a result, expensive table scans can result if this view criteria item is supposed to be the driving filter. You should make sure there are appropriate function indexes to support case-insensitive searches.

57.2.2.14 Avoid View Object Leaks

In general, avoid creating a view object at runtime. You should add the view object instance to the application module and let the framework create it for you. If you have a use case where you must call createViewObject to create the view object, you should explicitly give it a name and first check if a view object with that name already exists in the application module. If it is already there, you should reuse it rather than create another one. If you no longer intend to use a dynamically created view object, remove it from the application module to avoid memory leaks.

57.2.2.15 Provide a "Smart" Filter when Using LOV Combobox

When you define a Combo Box with List of Values, you should provide an additional view criteria using the Filter Combo Box Using option so that the user only sees a list of frequently used choices. It typically does not meet business needs to return something like the first 10 customers in the system.

57.2.2.16 Use Small ListRangeSize for LOVs

When you define a LOV for a view object attribute, there is a ListRangeSize property (visible only in source), which defaults to 10. This controls the number of values to fetch when the combo box is selected on the UI. You should not change the ListRangeSize to a large value. In particular, -1 should never be used as it brings back all the rows.

57.2.2.17 Avoid Reference Entity Objects when not Needed

If your view object includes reference entity objects, they are loaded in via separate queries whenever the key column values are changed. Therefore, if you have a scenario where attributes from the reference entity objects are not needed, you should use a view object that do not include reference entity objects. An example of this is when you are programmatically inserting rows and the reference entity object attributes do not need to be shown.

57.2.2.18 Do Not Use the "All at Once" Fetch Mode in View Objects

If you select the "All at Once" view object fetch mode, the view object query returns all rows, even though you are looking at only the first row. Depending on the query, this could cause OutOfMemory errors as the result of too many rows being fetched. Use the default "As Needed" fetch mode instead.

57.2.2.19 Do Not Use the "Query List Automatically" List of Value Setting

If you use the "Query List Automatically" option in the UI hints panel in the edit LOV screen, a query is executed by default, which could be expensive. This setting impacts only whether a search is executed by default when the LOV search list displays. For LOV combo boxes, regardless of this setting, the smart filter executes when the LOV combo is clicked and the dropdown list displays.

57.2.2.20 Avoid the "CONTAINS" or "ENDSWITH" Operator for Required or Selectively Required View Criteria Items

Required or Selectively Required view criteria items should use indexes so that their queries are efficient. If you use the "CONTAINS" or the "ENDSWITH" operator on a view criteria, the indexes cannot be used efficiently, resulting in poor query performance. Use an "Equals" or "Starts With" operator instead.

57.2.3 Working with Application Modules

When working with application modules, consider the following suggestions for improving performance.

57.2.3.1 Enable Lazy Delivery

When the Lazy Delivery option is enabled, ADF Business Components defers the creation of view object instances and nested application modules until they are requested. For more information, see the "Data Delivery - Lazy versus Immediate" section in the Oracle Fusion Middleware Performance and Tuning Guide.

57.2.3.2 Make Application Code Passivation-Safe

An application module is an ADF Business Components container that encapsulates business service methods and active data model for a logical unit of work related to an end-user task. It is a wrapper for view objects and entity objects in a business model, handles all database transactions, and performs custom tasks by invoking application module specific service methods. For an ADF Business Components based web application, a HTTP request, if related to data operation, can not be processed without involvement of an application module instance. Figure 57-6 illustrates the application module position in the Oracle ADF applications architecture.

Figure 57-6 Oracle ADF Applications Architecture — Application Module Functions

Oracle ADF Apps Architecture - AM Functions

Application module state management and application module pooling are very important features provided out of the box by Oracle ADF. The combination of application module state management and pooling makes ADF Business Components based web application more scalable by multiplexing application module instances in pool to serve large volume concurrent HTTP user sessions, and more reliable (failover) by serializing pending user session state into persistent storage (Database or File system).

Passivation is the process of serializing current states of an active application module instance to persist it to make it passive. Activation is its reverse process to activate a passive application module instance.

After coding and debugging all functional issues of an ADF Business Components based application, it is necessary to disable application module pooling to test and verify the application code is passivation-safe. Disabling application module pooling enforces the application module instance to be released at the end of each request and be immediately removed (destructed), and passivation is triggered before its removal. On subsequent requests made by the same user session, a new application module instance must be created to handle this user request. A pending state must be restored from the passivation storage to activate this application module instance.

To disable application pooling for all application module pools, add -Djbo.ampool.doampooling=false to the JVM options when you run your page. You can also disable application pooling for select application modules.

To disable application pooling for select application modules:

  1. Launch Oracle JDeveloper.

  2. Right-click Application Module and select Configurations.

  3. Click Edit, and select the Pooling and Scalability tab. See Figure 57-7.

  4. Deselect Enable Application Module Pooling.

Figure 57-7 Application Module: Configurations — Pooling and Scalability

AM Configurations - Pooling Scalability

For more information about application module state management, see the "Application State Management" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

For more information about application module pooling, see the "Tuning Application Module Pools and Connection Pools" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.2.3.3 Avoid Passivating Read-Only View Objects

There is performance overhead associated with passivation and activation. It is important to know of cases of where not to use this feature without impacting scalability and reliability. For example, there is no need to passivate LOV view objects and validator view objects where the bind values are coming from the target data row via the view accessor relationship. Similarly, if you have View objects where none of the attribute values are used across requests, such as view objects used only in service calls, then you should disable passivation.

To disable passivation for a view object, uncheck the Passivate State option in the View Object Editor, as shown in Figure 57-8.

Figure 57-8 View Object Editor — Tuning

View Object Editor - Tuning

57.2.3.4 Avoid Passivating Certain Transient Attributes of a View Object

In addition to read-only view objects, some transient values of a view object, including transient view object attribute and calculated view object attribute, are read-only or their values are derived from other attributes via getter or groovy logic. There is no need passivate them.

  • A transient view object attribute is an attribute which is not mapped to a table column or SQL calculation value, but its value is provided by Accessor function code.

  • A calculated view object attribute is an attribute which is not mapped to a table column but is a SQL calculation expression.

To disable passivation for a subset of view objects' transient values, deselect Include All Transient Values in the View Object Editor - Tuning section as shown in Figure 57-8. Then check the Passivate check box only for the attributes that require passivation in the view object attribute editor.

For more information, see "Managing the State of View Objects" in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.2.3.5 Maintain Application Session User Tables

By default, Oracle ADF takes care of passivating all necessary states of an application module instance, but some custom information must be addressed by application code. Some examples of custom information are:

  • Private member fields in view objects, entity objects, or application modules.

  • User session state cached in Application Session UserData hash table. Go through:

    ApplicationModule.getDBTransaction().getSession().getUserData()
    

    Caution:

    This is not the user session. This is the Application Module session that ties to an application module and is maintained by ADF Business Components.

It is easy to confuse an Application Module session object with an HTTPSession object, which also provides a hash table to cache some session user information. The difference is the HTTPSession exists at the ADF Controller layer and the state cached in it can be across HTTP requests independent of the application module instance. On the other hand, Application Session exists at the ADF Model layer and is per application module instance, so state cached in it can not be across HTTP requests once the application module instance switching happens.

It is strongly suggested to avoid saving a lot of custom session states in HTTPSession because it increases memory usage and impacts scalability. This is the exact problem that application module state management and application module pooling is expected to solve.

To handle custom session states, you need to override passivateState() and activateState() functions in your ApplicationModuleImpl class or relevant VOImpl class.

For more information about how to manage custom user information, see the "Application State Management" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

The following is sample code from the Pathfinder Application to passivate and activate the UserLoginPrincipal object. Example 57-2 shows one way that you can passivate custom state.

Note:

XML documents can only handle String. Therefore, an object must be serialized before saving.

Example 57-2 Passivating and Activating UserLoginPrincipal

public void passivateState(Document doc, Element parent)
  { 
      super.passivateState(doc,parent);
 
      UserLoginPrincipal principal = (UserLoginPrincipal) getSession().getUserData().get(Constants.LOGIN_PRINCIPAL);
 
      ByteArrayOutputStream baos=new ByteArrayOutputStream();

      try{
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(principal);
        oos.flush();
      }
      catch(IOException e)
      {}

      String strPrincipal = baos.toString();

      Node node = doc.createElement(Constants.LOGIN_PRINCIPAL);   // Login should easily be converted into String  
      Node cNode = doc.createCDATASection(Constants.LOGIN_PRINCIPAL);
      cNode.setNodeValue(strPrincipal);
      node.appendChild(cNode);  
      parent.appendChild(node);

  }
  
  public void activateState(Element elem)
  {
    super.activateState(elem);
    if (elem != null) {
          NodeList nl = elem.getElementsByTagName(Constants.LOGIN_PRINCIPAL);  // no idea what tags can be used
          if (nl != null) {
              for (int i=0, length = nl.getLength(); i < length; i++)
                  {
                          Node child = nl.item(i).getFirstChild();
                          if (child != null) {

                            String strPrincipal = (String)child.getNodeValue();
                            ByteArrayInputStream bais=new ByteArrayInputStream(strPrincipal.getBytes());
                            ObjectInputStream ois;
                            UserLoginPrincipal principal = null;                            
                            try
                            {
                              ois = new ObjectInputStream(bais);
                              principal = (UserLoginPrincipal)ois.readObject();
                            }
                            catch (IOException e)
                            {}
                            catch (ClassNotFoundException e)
                            {}
                            getSession().getUserData().put(Constants.LOGIN_PRINCIPAL,principal);
                            break;
                          }
                  }
          }
      }
  }

57.2.3.6 Tune the Application Module Release Level

The default release level is Managed, which implies that the application module's state is relevant and has to be preserved for this data control to span over several HTTP requests. In some cases you can programmatically set the release level to Unmanaged ("Stateless") at run time for particular pages to achieve better performance (no passivation). A classic example is the Logout page. Usually you can programmatically release the application module with the unmanaged level when you want to signal that the user has ended a logical unit of work.

Caution:

When using DCDataControl::resetState() to set an Unmanaged release level, it only affects the current application module instance in the current request. For the next request, the application module instance automatically uses the default Managed release level again.

Setting the release level to Reserved makes Data Control "sticky" to an application module instance and all requests from the same HTTPSession associated with this Data Control are served by the same application module instance. This is contrary to the initiative of introducing application module state management and application module pooling, so using this release level is strongly discouraged.

Caution:

Once the release level is changed to Reserved by calling DCJboDataControl::setReleaseLevel() with input argument ApplicationModule.RELEASE_LEVEL_RESERVED, it stays at this level until explicitly changed.

Table 57-1 illustrates application module release mode comparisons.

Table 57-1 Application Module Release Mode Comparison

Release Mode Unmanaged (Stateless) Managed (Stateful) Reserved

Application Module Behavior

Does not preserve the state of the application module instance between page-processing requests. The instance is immediately released when a JavaServer Page (JSP) page terminates.

Note: Select this option when you expect many users to access your JSP application simultaneously. The stateless option allows more users to access a JSP application simultaneously at the cost of requiring the user to reconnect to a new application module instance every time a JSP page is invoked (or re-invoked).

Preserves the application module instance's state in the database between page-processing requests. This permits the application to maintain a user's data without involving a single application module instance for long periods of time.

Note: Stateful mode provides failover support for the HTTP session and is the preferred choice when the application module uses a standard JDBC connection.

Allocates the application module instance for the duration of the browser session. The instance is released only at the end of the session. This mode is provided primarily for compatibility with certain application module definitions. Failover is not supported in this mode.

Note: Reserved mode is primarily useful when the application module requires a non-standard JDBC connection definition. Failover is not supported in this mode.

DBTransaction & User Action

Oracle ADF automatically posts and commits any changes because the application module state is not maintained between requests in stateless mode. The user is not expected to initiate the commit in stateless mode: the Commit and Rollback buttons are disabled in the JSP page.

Oracle ADF merely saves the application module state, including the data changes, to the database at the end of a page request. In this mode, the user is expected to initiate the commit by clicking the Commit button in the process JSP page. Once the user clicks the Commit button, Oracle ADF immediately initiates a post and commit (together, as one step) on the database. Optionally, the user can click the Rollback button to prevent their changes from entering the database without ever initiating a post. Because the application module state is preserved, the user can initiate the Commit or Rollback at any point during the HTTP session.

Oracle ADF automatically posts any changes to the database (and initiates DML-specified database triggers on the effected tables). In this mode, the user is expected to click either the Commit button or Rollback button in the process JSP page. Because the application module itself is not released for the duration of the HTTP session, the user can initiate the Commit or Rollback at any point.

Application Module Locking Behavior

In stateless mode, it is recommended that the Business Components property jbo.locking.mode should be set to optimistic. Pessimistic locking is not compatible with stateless mode because the database transaction is always rolled back to allow the connection to be reused by another user. This results in the lock being released and makes pessimistic locking unusable.

In stateful mode, it is recommended that the Business Components property jbo.locking.mode should be set to optimistic. Pessimistic locking is not compatible with stateful mode because after the application module is preserved, the database transaction is rolled back to allow the connection to be reused by another user. This results in the lock being released and makes pessimistic locking unusable.

In release mode, you can reliably use pessimistic locking and may set the property jbo.locking.mode to pessimistic.


For more information about application module release level and state management, see the "Application State Management" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.2.3.7 Do Not Leave Uncommitted Database Updates Across Requests

If you make database updates during a request, using either a DBTransactionImpl.pst changes call or PLSQL, ensure the changes are committed within the same request, or rolled back if there are errors. All exceptions must be caught and rolled back to prevent partial updates from lingering in the database.

57.2.3.8 Release Dynamically Created Root Application Modules

If you create an application module using createRootApplicationModule calls, you should call the releaseRootApplicationModule to avoid a memory leak. Oracle ADF internally maintains references to these application modules, so they are not freed until you release them. You must also call releaseRootApplicationModule if you call one of the *AMImpl.getInstance calls for the various Applcore application modules.

57.2.3.9 Do Not Destroy the Application Module when Calling Configuration.releaseRoot ApplicationModule.

Call Configuration.releaseRootApplicationModule(am, false) instead of Configuration.releaseRootApplicationModule(am, true). If true is passed, the application module is destroyed and the next request for this application module will be expensive because it needs to be created. If false is passed, the application module is released back to the application module pool and the next request can simply check out the application module from the pool, thereby avoiding the creation cost.

57.2.4 Working with Services

When working with services, consider the following suggestions for improving performance.

57.2.4.1 Set the Find Criteria to Fetch Only Attributes that are Needed

By default, when you call the find service, the child service data objects are also fetched. If you do not need those children, then make sure you set the find criteria to fetch only the attributes you need.

Example 57-3 is sample code showing how to create a find criteria.

Example 57-3 How to Create Find Criteria

FindCriteria fc = (FindCriteria)DataFactory.INSTANCE.create(FindCriteria.class);
  //create the view criteria item
  List value = new ArrayList();
  value.add(new integer(10));
  ViewCriteriaItem vci = (ViewCriteriaItem)DataFactory.INSTANVCE.create(ViewCriteraItem.class);
  vci.setValue(value);
  vci.setAttribute("Deptno");
  List<ViewCriteriaItem> items = new ArrayList(1);
  items.add(vci);
  //create view criteria row
  ViewCriteriaRow vcr = (ViewCriteriaRow)DataFactory.INSTANCE.create(ViewCriteriaRow.class);
  vcr.setItem(items);
  //create the view criteria
  List group = new ArrayList();
  group.add(vcr);
  ViewCriteria vc = (ViewCriteria)DataFactory.INSTANCE.create(ViewCriteria.class);
  vc.setGroup(group);
  //set filter
  fc.setFilter(vc);

  List cfcl = new ArrayList();
  ChildFindCriteria cfc = (ChildFindCriteria)DataFactory.INSTANCE.create(ChildFindCriteria.class);
  cfc.setChildAttrName("Emp");
  cfc.setFetchStart(1);
  cfc.setFetchSize(1);
  cfcl.add(cfc);
  fc.setChildFindCriteria(cfcl);
  DeptResult dres = svc.fndDept(fc, null);
  pw.println("### Dept 10 and 2nd Emp ###");
.......

57.2.4.2 Expose Service for Frequently Used Logical Entities

If you are doing frequent fetches of a business entity that is not the top level of a business object, it is better to expose a find service for that business entity rather than expose a find service for the highest level. Otherwise, the service call must be made against the topmost level entity, incurring unnecessary cost.

57.2.4.3 Use Correct ChangeOperation when Calling a Service

When you are using the processXXX() method to insert new rows, call the processXXX() method using ChangeOperation.CREATE. Do not use ChangeOperation.MERGE. Calling the processXXX() method with ChangeOperation.MERGE issues extra queries to the database to check if the rows already exist.

57.2.4.4 Set Only Changed Columns on Service Data Objects for Update

When creating a list of service data objects to pass for update using the processXXX() method, if possible, set only the columns that you really need to change. Service data objects with fewer attributes that have been set are updated faster than service data objects with all the attributes set.

57.3 ADF ViewController Layer Guidelines

Follow the best practices described in this section while working with various ADF ViewController layer components such as geometry management, page templates, and partial page refresh.

57.3.1 Working with Various ADF ViewController Components

When working with ADF ViewController components, consider the following suggestions for improving performance.

57.3.1.1 Minimize the Number of Application Module Data Controls

For a specific page or page fragment, try to use only one application module data control. You should use nested application modules rather than a separate application module data control because this minimizes the number of database connections your page uses. When using a nested application module, be sure to drag the nested application module from under the root application module in the data control panel.

Note:

If you use nested application modules, you can not pull data from different databases.

57.3.1.2 Use the Visible and Rendered Attributes

All ADF Faces Rich Client display components have two properties that relate to whether the component is displayed on the page. For more information about how to use these properties, see "ADF Faces Component Attributes" in the Oracle Fusion Middleware Performance and Tuning Guide.

57.3.1.3 Remove Unused Items from Page Bindings

If you decide to remove an unused item, such as a column from a table, remove the corresponding item from the tree binding. If you forget to do so for an expensive computed attribute, the logic to compute the attribute still executes even after removing the computed attribute from the table. In addition, remove any unused iterator bindings from the page definition file.

57.3.1.4 Disable Column Stretching

Columns in the table and treeTable components can be stretched so that there is no unused space between the end of the last column and the edge of the table or treeTable component. This feature is turned off by default due to high performance impact. Turning this feature on has a performance impact on the client rendering time so it should not be used for complex tables.

57.3.1.5 Use Appropriate Values for Refresh and RefreshCondition

The default values of the Refresh and RefreshCondition properties of iterator binding and action binding are deferred and NULL, which means that the related action binding will be invoked only if needed. The default value is appropriate for most cases. If you select the value ifNeeded for the Refresh property, the iterator or action may get refreshed twice and therefore impact performance. Figure 57-9 shows how the JavaServer Faces (JSF) and Oracle ADF phases integrate in the lifecycle of a page request.

Figure 57-9 Lifecycle of a Page Request in an Oracle Fusion Web Application

Lifecycle of a Page Request in a Web App.

In particular, the value always should not be used as the Refresh property for invokeAction bindings. Using ifNeeded is the best option for most cases. Note that if invokeAaction binds to the methodAction, which does not accept any parameters, or to any action then it will fire twice per request. To avoid this situation, use the RefreshCondition attribute on invokeAction to control when the action needs to fire.

For more information about the Refresh property, see the What You May Need to Know About Using the Refresh Property Correctly section in the "Understanding the Fusion Page Lifecycle" chapter of the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.3.1.6 Disable Estimated Row Count if Necessary

In addition to the query used to fetch the data for display, Oracle ADF issues a count query to calculate the estimated result set size for view objects that are bound to a table on the UI. This is used to size the scroll bar and is capped at a certain threshold to avoid scanning the entire result set. If your query is expensive and this additional query results in your page not meeting your performance target, consider setting the RowCountThreshold setting to a negative value. This turns off the row count query.

You consider disabling row count completely by setting RowCountThreshold to -1 after extensive tuning. Then you could apply the global RowCountThreshold if your count query still has performance issues.

Caution:

When you disable the estimated row count, the scrolling behavior of your table is different. The user can scroll forward only one range at a time.

57.3.1.7 Use HTTPSession Hash Table in Moderation

HTTPSession provides a hash table to cache user information. However, all the information is saved in memory, so inappropriate use of HTTPSession cache causes some scalability issues, including:

  • High memory usage on the server

  • User information loss if the server is down

  • Increased network traffic to replicate session state in a clustered environment

Putting critical, large volume information in HTTPSession cache is not recommended. Instead, you should leverage application module state management and application module pooling. See Section 57.2.3.2, "Make Application Code Passivation-Safe."

Example 57-4 shows how to use HTTPSession cache in a backing bean.

Example 57-4 HTTPSession Cache in a Backing Bean

((HttpServletRequest)
  (FacesContext.getCurrentInstance().getExternalContext().getRequest())))
  .getSession().setAttribute("UserLoginPrincipal",sessionLoginPrincipal);

57.3.1.8 Use Short Component IDs

Sometimes you must provide an ID for a UI component. For example, an ID is required for a component that is a source of a partial page refresh (PPR) event. Also, Oracle ADF generates default component IDs for certain components, such as when a task flow is added to a page as a region. (The default region ID is the first 5 characters of the task flow name plus a digit). If you have pages with a region ID that is greater than 7 characters, you should shorten the IDs of the task flow regions to 7 characters or fewer (including the digit), with 3 characters being ideal.

If IDs are specified for other naming containers (such as tables), a length of 3 or fewer is best. Using short naming for container IDs helps to reduce the size of each response, as well as network traffic, because the IDs of the parent naming containers are appended to a child's generated ID.

57.3.1.9 Follow UI Standards when Using Search

When using Search, follow these UI standards:

  • Blind queries are not allowed.

  • Match All should be used instead of Match Any when there are multiple criteria.

57.3.1.10 Avoid Executing Component Subtree by Adding a Condition Check

In some cases it is possible to find out during the jsp tag execution phase if a particular jsp subtree needs to be executed or not by using the <c:if test...> tag. Example 57-5 is an example for panelAccordion. (Note the use of $ instead of #).

Note:

Using this technique is not recommended. For other techniques, see Section 57.3.1.20, Section 57.3.1.21, and Section 57.3.1.22.

Example 57-5 Using the <c:if test...> Tag

<af:panelAccordion ......>
  <af:showDetailItem disclosed="#{item.disclosed}" .......>
    <c:if test="${item.disclosed}">
      <!--Content here will not be executed in jsp engine if item is not disclosed-->
    </c:if>
  </af:showDetailItem>
</af:panelAccordion>

Example 57-6 shows how to use this technique with lazy popups.

Example 57-6 Using <c:if test...> Tag with Lazy Popups

<af:popup id="popupRegion" contentDelivery="lazyUncached"
                  launcherVar="source" eventContext="launcher">
       <!-- param passed to taskflow -->
       <af:setPropertyListener from="#{source.attributes.param}"
                                  to="#{requestScope.param}" type="popupFetch"/>
       <!-- reset taskflow and turn on activateSubtree (session scoped) -->
       <af:setPropertyListener from="Y" to="#{testBean.refreshTaskflow}"
                                  type="popupFetch"/>
       <af:panelWindow id="window" title="Task Flow">
            <!-- conditionally includes the nested components -->
            <c:if test="${testBean.activateSubtree}">
              <af:region value="#{bindings.dynamicRegion1.regionModel}"
                         id="dynamicRegion1"
                         regionNavigationListener="#{testBean.navigationListener}"/>
            </c:if>
       </af:panelWindow>
       
        <!-- toggles off the activateSubtree flag -->
        <af:serverListener type="serverPopupClosed"
                             method="#{testBean.popupClosedListener}"/>
        <!-- queues a custom event on close of the popup to invoke the serverListener -->
        <af:clientListener method="popupClosedListener" type="popupClosed"/>
</af:popup>

57.3.1.11 Do not set Client Component Property to True

ADF Rich Client has a sparse component tree on the client. This means only required components are created on the client. The component instance is instantiated on the client if:

  • Client component property is true by default. For example, as required by Oracle ADF

  • Client-side event listener is registered, which you should not be using

  • Due to needed client-side interaction with component, the client component property is set to true

To achieve the best performance, do not set the client component property to true.

57.3.1.12 Set Immediate Property to True when Appropriate

ADF Rich Client components have an immediate attribute. There are some cases where setting immediate to true can lead to better performance. For more information, see the "ADF Faces Component Attributes" section in the Oracle Fusion Middleware Performance and Tuning Guide.

57.3.1.13 Use Appropriate ContentDelivery Mode for a Table or a Tree Table

By default, the data for Table, Tree and other stamped components uses the lazy data delivery mechanism. This means that page content is delivered with the first response from the server and the next request fetches the data. This option should be used when the page has enough content to be displayed and a table query may be slow. Underneath, the data fetch request uses the table streaming feature, which delivers table data to the client as soon as it is available. Also, it provides the ability to execute data fetch requests on the server in parallel, making them faster. To enable fetching data in parallel, set the RenderHint property of the iterator to background. This option could increase the number of database connections.

The other option to deliver data is immediate mode, which is set on the table. In this mode, the data is delivered with the initial page. This is better in terms of CPU and memory consumption on the server, and should be used if the table is the main context of the page.

For more information, see "Data Delivery - Lazy versus Immediate" in the Oracle Fusion Middleware Performance and Tuning Guide

57.3.1.14 Set the Appropriate Fetch Size for a Table

Tables have a fetch size which defines the number of rows to be sent to the client in one round-trip. To get the best performance, keep this number low while still allowing enough rows to fulfill the initial table view port. This ensures the best performance while eliminating extra server requests.

In addition, consider keeping the table fetch size and iterator range size in sync. By default, the table fetch size is set to the EL expression #{bindings.<name>.rangeSize} and should be equal to the iterator size. The iterator range size should be set to number of displayed rows + 1. In particular, for auto-height tables, you should set iterator range size to the value of autoHeightRows + 1.

57.3.1.15 Avoid Frozen Columns and Header Columns if Possible

Frozen columns and header columns in the table are very expensive on the client side and should be avoided if possible. Overhead can be 20% to over 100% for a simple page with limited content when there are frozen columns. If frozen columns must be used, make sure the row height of the columns to the left and the right of the frozen column are of the same height.

57.3.1.16 Avoid Unnecessary Regions

Regions are very powerful and provide extreme flexibility. However, there is an associated cost with every region. In order to have the best performance, make sure to use the region only when it is needed.

Generally, the Oracle ADF guideline is to not have more the 15 regions on the page.

57.3.1.17 Set the Data Control Scope to "Shared"

Set the Data Control Scope to Shared for a task flow to allow sharing of the data control. (This is the default). This reduces the number of database connection. There may be some cases where using Isolated is functionally necessary. For more information, see the "Sharing Data Control Instances" section in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.3.1.18 Select the No Save Point Option on a Task Flow when Appropriate

Select the No Save Point option if you do not need the functionality to roll back changes at the end of the task flow. If you do not use this option, the model state is passivate at the beginning of the task flow, which is expensive.

57.3.1.19 Use Click-To-Edit Tables when Appropriate

For tables where most rows are usually view-only and rarely edited, set the Edit mode property to Click-To-Edit. This reduces the response size significantly and improves performance.

57.3.1.20 Avoid Unnecessary Task Flow Activation for Regions Under Popups

By default, task flows that use a region under popups are activated when the page loads, not when the popup displays. This causes unnecessary memory and CPU usage if the user does not use the popup. There are two approaches for activating the task flow region only when the popup displays:

  1. Set the following properties to "deferred":

    • The childCreation property on the popup.

    • The activation property on the task flow binding. (This is under the Executables section in the page definition file.)

  2. Set the activation property on the task flow binding to "conditional" and specify a condition in the "active" to an EL expression that returns true when the popup displays. Usually this requires creating a view scope variable that is set using a setPropertyListener executed on popupFetch. The EL expression must return true as long as the popup is displayed. (A request scope variable will not work in most cases unless you cannot submit any server requests from the popup.)

Approach (1) is simpler but you must use approach (2) for these cases:

  • Any of the following tags are present inside the popup attribute:

    • f:attribute

    • af:setPropertyListener

    • af:clientListener

    • af:serverListener

  • You need to refer to any child components of the popup before the popup is displayed. Setting childCreation="deferred" postpones the creation of any child components of the popup and you cannot refer to them until after the popup displays.

57.3.1.21 Delay Creation of Popup Child Components

This recommendation is similar to Section 57.3.1.20, "Avoid Unnecessary Task Flow Activation for Regions Under Popups", but is applicable to popups that do not contain regions. By default, the child components under a popup are created even when the popup is not accessed. This causes unnecessary memory and CPU usage if the user does not use the popup. To avoid this overhead, set the childCreation property on the popup to "deferred".

This approach cannot be used for these cases:

  • Any of the following tags are present inside the popup attribute:

    • f:attribute

    • af:setPropertyListener

    • af:clientListener

    • af:serverListener

  • You need to refer to any child components of the popup before the popup is displayed. Setting childCreation="deferred" postpones the creation of any child components of the popup and you cannot refer to them until after the popup displays.

57.3.1.22 Avoid Unnecessary Task Flow Activation for Regions Under Switchers

By default, task flows that use an af:region under switchers are activated regardless of whether the facet displays. This causes unnecessary memory and CPU usage for the facets that do not display. To activate the task flow region only when it displays, set the "activation" property on the task flow binding to "conditional", under the Executables section in the page definition file. Also specify a condition in the "active" to an EL expression that returns true when the facet displays.

Typically, you may already have an EL expression to control the return value for the facetName property in the switcher. For example, if your switcher looks like this:

<af:switcher id="s1" defaultFacet="1" facetName="#{pageFlowScope.facet}"><f:facet name="1"><af:region value="#{bindings.TF1.regionModel}" id="r1"/></f:facet><f:facet name="2"><af:region value="#{bindings.TF2.regionModel}" id="r2"/></f:facet></af:switcher>

The associated binding should have activation set to "conditional", and active set to an EL, as follows:

<taskFlow id="tTF1" taskFlowId="<some task flow>" 
active="#{pageFlowScope.facet=='1'}" activation="conditional" 
xmlns="http://xmlns.oracle.com/adf/controller/binding"/>

<taskFlow id="tTF2" taskFlowId="<some other task flow>" 
active="#{pageFlowScope.facet=='2'}" activation="conditional" 
xmlns="http://xmlns.oracle.com/adf/controller/binding"/>

57.3.1.23 Avoid Unnecessary Root Application Module Creation from UI-layer Code

Creating additional root application modules is expensive when you can reuse the root application module that is associated with the data bindings on your page. For example, do not access an application module instance by calling the Configuration.createRootApplicationModule() API from a backing bean. This results in creating additional application module instances which are distinct from the application module instance that is automatically checked out and in by the Oracle ADF data binding layer, used by UI pages and task flow method call activities. This can lead to performance and memory issues if your backing bean calls Configuration.createRootApplicationModule() API without calling releaseRootApplicationModule().

You should use an ADFM action binding to invoke a client interface method declaratively on an application module instead. This approach requires no code and often prevents the need for a backing bean. It also ensures that any exceptions are handled in a consistent way as if Oracle ADF had invoked the method declaratively. You should also ensure that your backing bean is invoked in a context where a pageDef has been defined.

The following code excerpt is an example that follows our recommendation:

private ComponentReference<RichTable> allocationTableRef;
public void setAllocationTable(RichTable allocationTable) {
if( this.allocationTableRef == null)
this.allocationTableRef =
ComponentReference.newUIComponentReference(allocationTable);}
public RichTable getAllocationTable() {
return allocationTableRef==null ? null : allocationTableRef.getComponent();

The following example is not recommended:

private RichTable allocationTable;
public void setAllocationTable(RichTable allocationTable) {
this.allocationTable = allocationTable; }
public RichTable getAllocationTable() {
return allocationTable; }

57.3.1.24 Avoid Unnecessary Savepoints on Task Flow Entry

When the transaction setting of a task flow is "Always Use Existing Transaction" or "Reuse Existing Transaction if Possible", and the "No savepoint on taskflow entry" box is not checked, Oracle ADF automatically creates a savepoint when entering the taskflow. You should check the box to avoid the savepoint cost if you do not have functionality to rollback to this particular savepoint.

57.3.1.25 Cache Return Values in Backing Bean Getters

The common usage of backing beans is to reference values from EL expressions. Bean getters may also be called from other places in the code, as well as being called multiple times in a request. If you have expensive computations inside the bean getter logic, consider caching the results inside the bean. This should be fairly safe to do for request-scope beans unless you expect the result to change within the request. For view-scope or page flow-scope beans, be careful about when to invalidate the cached results.

57.3.1.26 Do Not Maintain References to UI Components in Managed Beans

If you maintain direct references to UI Component instances from view scope or pageflow scope beans, this could cause both functional errors and impact performance. If you must maintain a reference, use the ComponentReference pattern instead.

57.3.2 Enable ADF Rich Client Geometry Management

ADF Rich Client supports Geometry Management of the browser layout where parent components in the UI explicitly size the children components to stretch and fill up available space in the browser. While this feature makes the UI look better, it has a cost. For more information, see the "Enable ADF rich client geometry management" row in the "Configuration Parameters for ADF Faces" table in the Oracle Fusion Middleware Performance and Tuning Guide.

57.3.3 Use Page Templates

Page templates allow you to build reusable, data-bound templates that can be used as a shell for any page. For important considerations when using templates, see the "Use page templates" row in the "Configuration Parameters for ADF Faces" table in the Oracle Fusion Middleware Performance and Tuning Guide.

57.3.4 Use ADF Rich Client Partial Page Rendering (PPR)

You should always consider using partial page refresh instead of a full page refresh. For more information, see the "Use ADF Rich Client Partial Page Rendering" row in the "Configuration Parameters for ADF Faces" table in the Oracle Fusion Middleware Performance and Tuning Guide.

57.4 SOA Guidelines for Human Workflow and Approval Management Extensions

For best practices while working with Human Workflow and Approval Management extensions (AMX), see the Oracle Human Workflow Performance Tuning chapter in Oracle Fusion Middleware Performance and Tuning Guide.

57.5 Oracle Fusion Middleware Extensions for Applications Guidelines

When working with application modules, consider these best practices related to using a nested service and releasing application modules returned from getInstance calls.

57.5.1 Use Profile.get to Get Profile Option Values

To get a profile option value, use

oracle.apps.fnd.applcore.Profile.get(<Profile Option Name>)

This is optimized to first find the profile value in an internal cache, so it checks out an application module only if needed. Avoid calling ProfileServiceAM.getInstance as it checks out a ProfileService application module instance, which is expensive.

57.5.2 Release any Application Modules Returned from getInstance Calls

If you have no other option and must use getInstance to get an application module back, such as ProfileServiceAM.getInstance, you must release it to avoid a memory leak via a Configuration.releaseRootApplicationModule call as shown in Example 57-7.

Example 57-7 Release Application Module

ProfileServiceAM profileService = null;

try {
  profileService = ProfileServiceAMImpl.getInstance();
  String value = profileService.getProfileValue("<ProfileOptionName>");
}
finally {
  Configuration.releaseRootApplicationModule(profileService, false);
}

57.5.3 Avoid Unnecessary Activation of Attachments Taskflow

When the attachments feature is used, it creates a new taskflow in the page bindings. For example:

<taskFlow id="attachmentRepositoryBrowseTaskFlow1"
taskFlowId="#{backingBeanScope.AttachmentBean.taskFlowId}"

or:

<taskFlow id="attachmentRepositoryBrowseTaskFlow1" 
taskFlowId="/WEB-INF/oracle/apps/fnd/applcore/attachments/ui/attachments-docpicker-taskflow.xml"

This task flow is unnecessarily activated. To avoid this situation, navigate to the bindings tab of the page where the Attachments component was added. Select attachments task flow, attachmentRepositoryBrowseTaskFlow1, from the list of Executables. Set the following attributes in the property inspector under Common:

  • activation="conditional"

  • active="#{pageFlowScope.FND_ATTACHMENTS_LOAD_TF==true}"

57.5.4 Use Static APIs on Message Get Message Text

Use static APIs from oracle.apps.fnd.applcore.messages.Message to get message text. Avoid using MessageServiceAMImpl.getInstance,or calling createRootApplicationModule to get MessageServiceAM, as this results in checking out and initializing an instance of MessageServiceAM from the AM pool, which has a cost.

57.5.5 Set the Data Control Scope to Isolated for Page Level Item Nodes

If the data control scope is shared for taskflows pointed to by certain item nodes, then the life span of these taskflow data controls is tied to the parent, which is either Main TF or Regional TF in the UI shell. This scenario applies to those item nodes with taskType equal to "defaultMain", "dynamicMain", or "defaultRegional". This means that DC frame is no removed for the duration of the session, regardless of any navigation or closing tab. This is due to the fact that Main TF and Regional TF in the UI shell has the DC scope set to shared, due to the requirement to share CE between the regional and main areas.

If there is no requirement to share transactional data between the regional and main areas, then set dataControlScope="isolated" on the page level item node in the menu file. This recommendation assumes that the underlying taskflows used in the regional area or the task menu already have data control scope set to isolated. Note that you should not change the data control scope on the taskflow itself.

57.6 General Java Guidelines

When working with Java, consider these best practices related to Strings and StringBuilder, Collections, Synchronization, as well as other Java features.

57.6.1 Working with Strings and StringBuilder

When working with Strings and StringBuilder, consider the following suggestions for improving performance.

57.6.1.1 Use StringBuilder Rather than the String Concatenation Operator (+)

When doing String concatenations inside a loop, see if the operation can be moved outside of the loop. Frequently, the concatenation code is put inside the loop even though the value can never change there.

The String concatenation operator + involves the following:

  • A new StringBuilder is created.

  • The two arguments are added to it with append().

  • The final result is converted back with a toString().

This increases cost in both space and time, especially if you're appending more than one String. You should consider using a StringBuilder directly instead.

StringBuilder was introduced in Java Development Kit (JDK) 1.5 and is more efficient than StringBuffer since the methods are not synchronized. When using StringBuilder (or StringBuffer), optimally size the StringBuilder instance based on the expected length of the elements you are appending to it. The default size of a StringBuilder is 16. When its capacity is exceeded, the JVM has to resize the StringBuilder which is expensive. For example, instead of:

String key = granteeType + ":" + granteeKey;

You should follow this example:

String key = new StringBuilder(granteeType.length() + 1 + 
  granteeKey.length()).append(granteeType).append(":").append(granteeKey)
  .toString();
 

This way, the StringBuilder object is initialized with the correct capacity so it can hold all the appended strings it needs to resize its internal storage structure.

For the sake of simplicity, it is acceptable to do String concatenation using "+" for debug log messages, as long as you follow the logging standard and check log level before constructing the log message.

Avoid unnecessary use of String.substring calls since it creates new String objects. For example, instead of doing this:

if (formattedNumericValue.substring(0,1).equals("-")) negValue = true;

Do this instead:

if (formattedNumericValue.charAt(0) == '-') negValue = true;

The hashCode method is another common place where you do String concatenation. Example 57-8 uses the hashCode implementation and requires String concatenation on every call.

Example 57-8 Hashcode with String Concatenation

public int hashCode()
{
  … … … 
    h = new StringBuffer(len).append(resp).append(rapl).toString().hashCode();
    return h;
}

Example 57-9 does not use String concatenation.

Example 57-9 Hashcode without String Concatenation

public int hashCode()
{
  … … …
  h = 37*h + (int)(m_respID ^ (m_respID >>> 32)); 
  h = 37*h + (int)(m_respApplID ^ (m_respApplID >>> 32));
  return h;
}

Note:

This example was taken from the book Effective Java.

Plan carefully before deciding to concatenate Strings. There are often alternative ways to implement the intended logic without concatenation.

57.6.1.2 Check the Log Level Before Making a Logging Call

You should always check the log level before you make a logging call, otherwise, many objects may be constructed unnecessarily. For example, if logging is disabled, but your code still calls the logging API that passes in the log message. This concatenates several String objects together and the String concatenation is a waste of resources.

The log message is constructed and passed into the logging API, and then discarded since logging is disabled. If you first check if the target log level is enabled, then the log message does not need to be created unless it is actually needed. For more information see the "Set Logging Levels" section in the Oracle Fusion Middleware Performance and Tuning Guide.

57.6.1.3 Use Proper Logging APIs for Debug Logging

Use proper logging APIs, such as AppsLogger, instead of using System.out.println and System.err.println for debug logging. This way, log messages are properly formatted with the correct context information.

57.6.1.4 Lazy Instantiation

Avoid instantiating objects until they are needed. For example, if you are coding a method to do String replacement, do not allocate a StringBuilder object to do the replacement until you have found a fragment that needs to be replaced. For more information, see the "Application Module Design Considerations" section in the Oracle Fusion Middleware Performance and Tuning Guide.

57.6.2 Configure Collections

When working with Collections, consider the following:

  • Legacy collections (like Vector and Hashtable) are synchronized, whereas new collections (like ArrayList and HashMap) are unsynchronized, and must be wrapped via Collections.SynchronizedList or Collections.synchronizedMap if synchronization is desired. Do not use synchronized classes collections, including collections from java.util.concurrent package, that are not shared among threads.

  • Do not use object collections for primitive data types. Use custom collection classes instead.

  • Size a collection based on the number of elements it is intended to hold to avoid frequent reallocations and rehashing in case of hashtables or hashmaps.

For more information about Collections, see "Configuring Garbage Collection" in the Oracle Fusion Middleware Performance and Tuning Guide.

57.6.3 Manage Synchronization

When working with synchronization methods you should consider the following:

Avoid synchronized methods if possible, because even with the latest versions of the JVM, there is still significant overhead.

Bad candidates for synchronization are:

  • Read-only objects

  • Thread local objects

Minimize the size of the synchronized block of code. For example, instead of synchronizing the entire method, it may be possible to synchronize only part of the method.

In JDK 1.5, there is a new package, java.util.concurrent, that contains many classes designed to reduce contention. For example, java.util.concurrent.ConcurrentHashMap provides efficient read access while still maintaining synchronized write access. These new classes should be evaluated instead of simply using a Hashtable whenever synchronization is required.

57.6.4 Work with Other Java Features

When working with Java features, you should consider the following:

57.6.4.1 Avoid Autoboxing

Autoboxing is a feature introduced in JDK 1.5, which allows direct assignment of primitive types to the corresponding object type, such as int => Integer. Avoid using autoboxing in code that is called repeatedly, as shown in this example:

Integer myInterger = 1000;

Example 57-10 shows how the compiled code basically creates a new Integer object based on the int value:

Example 57-10 Compiled Code

8: bipush 100
10: invokestatic #2;
//Method 
java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: astore_2

If this piece of code is called repeatedly, then each call creates one Integer object and could have adverse performance impact.

57.6.4.2 Do not use Exceptions for Code Path Execution

Exception object and snapshot of stack have to be created. This is expensive especially for typical Oracle ADF applications, which have very deep execution stacks. For example, if your code needs to detect whether a certain object can be casted to a certain type, use instanceOf instead of doing the cast and catching the exception. In other words, use instanceOf instead of relying on ClassCastException.

57.6.4.3 Reuse Pattern Object for Regular Expression Matches

If using regular expression classes to match against a known pattern, create the Pattern object only once and reuse it for subsequent matches. Only the Matcher object needs to be created each time.

57.6.4.4 Avoid Repeated Calls to the same APIs that have Non-Trivial Costs

Avoided repeated calls to the same APIs that have non-trivial costs. Use a local variable to hold the result instead. For example, instead of:

if (methodA() >= 0)
  return methodA() + methodB();

Use:

int res = methodA();
If (res >= 0)
  return res + methodB();

57.6.4.5 Close Unused JDBC Statements to Avoid Memory Leaks

To avoid memory leaks, closed unused JDBC statements. Example 57-11 depicts a statement leak in java code.

Example 57-11 Statement Leak in Java Code

String s1 = "BEGIN FND_GRANTS_PKG.UPDATE_GRANT("+
                    " :1, "+
                    " :2, "+
                    " :3, "+
                    " :4, "+
                    " :5);"+
                    " END;";
 
        setGrant = txn.createCallableStatement(s1,1);
 
        setGrant.setInt(1,v);
        setGrant.setString(2,guid);
        setGrant.setDate(3,sd);
        setGrant.setDate(4,edt);
        setGrant.registerOutParameter(5,Types.VARCHAR);
        setGrant.execute();

There should be a setGrant.close() call to close the statement.

For every call to createStatement(), prepareStatement(), prepareCall(), createCallableStatement(), or createPreparedStatement() there should be a close() to prevent memory leaks.

In the case of query execution, it is possible that the result set may be closed, but the underlying statement has not been closed, as shown in Example 57-12.

Example 57-12 Underlying Statement Not Closed

public static  AppsCtxtFileInfo readAppsCtxtFile(Connection pCon,
 AppsCtxtFileInfo fileInfo,boolean update)
  {
    AppsCtxtFileInfo ret = null;
    try
    {
 
      String query = getReadAppsCtxtFileString(false,update);
 
      PreparedStatement stmt = pCon.prepareStatement(query);
 
      stmt.setString(1,fileInfo.getNodeName());
      stmt.setString(2,fileInfo.getPath());
 
      ResultSet rs = stmt.executeQuery();
 
 
      if (rs.next())
      {
        ret = getAppsCtxtFileInfoFromResultSet(rs);
      }
 
      rs.close();
 
    }catch(SQLException e)
    {
      Logger.println(e,Logger.DEV_LEVEL);
      if (update)
      {
        if (e.getErrorCode()== ROW_LOCKED_ERROR_CODE )
        throw new RowLockedException();
      }
      else
        throw e;
    }
    return ret;
  }

In this case, the result set is being closed via rs.close(). However, the statement (stmt) has not been closed. For every statement opened, you should close it in the final block of a try-catch, as shown in Example 57-13.

Example 57-13 Close Statement Example

try
{
...
<open the statement>
<process the statement>
...
}
catch
{
...
<process any exceptions>
...
}
finally
{
...
  try
  {
   <close the statement>
  }
  catch
  {
  <process any exceptions>
  }
...
}

Make sure to catch exceptions around something like stmt.execute().

57.6.4.6 Use registerOutParameter to Specify Bind Types and Precisions

In Java files, whenever a callable statement is fired, such as in a begin-end block, the out bind types and precision have to be specified. This is done after creating the callable statement but before the query is executed. The method call to specify the type is called registerOutParameter(). This call should exist for every out bind in the callable statement regardless of its return type. There are two overloaded versions of this method call that can be used:

registerOutParameter(int paramIndex, int sqlType, int scale, int maxLength)
registerOutParameter(int paramIndex, int sqlType)

57.6.4.7 Avoid JDBC Connection Leaks

If you are getting a connection directly from Data Source or through ApplSessionUtil.getConnection, make sure you release the connection in a final block.

57.7 Caching Data

Caching is one of the most common approaches for improving the performance of accessing commonly used data. Shared application module and view object provide a mechanism for storing database results and other objects, such as in-memory ADF Business Components objects for repeated usage. This minimizes expensive object initializations and database round-trips, which ultimately results in improved application performance.

57.7.1 Identifying Data to Cache

It is important to correctly identify the best data to cache. Generally, this is the data that is common to different users, frequently used and infrequently changed, expensive to retrieve from the database or data source, and expensive to create. Data suitable for caching should have some or all of the following characteristics:

  • Shared Objects: Data that is common across users is more appropriate than user specific data.

  • Long-Lived: Data that is long-lived is more appropriate than short-lived data that is valid only for a user request.

  • Expensive to Retrieve: Objects that take a long time to retrieve, such as objects that are obtained from expensive SQL queries, are good candidates.

  • Expensive to Create: Objects that are frequently created or take a long time to create are appropriate. Frequent creation can be avoided by caching the instances.

  • Frequent Use: Objects that have a high probability of being frequently used are appropriate. Caching objects that are not actively used needlessly occupy JVM memory.

  • Infrequently Changed: The cached data is invalidated and removed from cache whenever it is changed, which makes caching frequently changed data more costly.

Lookup codes are an example of data that meets most of the above criteria.

57.7.2 How to Add Data to Cache

Cached objects are stored in view objects, which are added to an application module. This application module is configured as shared at the application level so that the cached objects are available to all users. To stripe cached data, view criteria with bind parameters are used. For example, a view accessor on top of a shared DeptVO with a view criteria such as location=:bindLocation results in one cache for each distinct value of :bindLocation.

To add data to cache:

  1. Create a view object or identify an existing view object to store the cached data.

  2. (Optional) Create commonly used view criteria for the shared view object.

  3. (Optional) Configure the shared view object's property, such as time to live, and so on.

  4. Create one application module for each product that contains all the view objects to store the cached data.

    For information about how to create a shared application module, see the "Sharing Application Module View Instances" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

  5. Cache a short list of data by pre-loading all the data into memory. This prevents subsequent queries requiring additional database trips. To do this, generate the VOImpl class for the shared view object and override the create() function of VOImpl to fully populate the view object cache as shown in Example 57-14.

    Example 57-14 Pre-load all Data into Memory

    protected void create()
    {
    super.create();
    setRangeSize(-1);
    executeQuery();
    getAllRowsInRange();
    }
    

    Note:

    This step is optional as there may be caches, such as profile cache, that you would want to populate lazily.

57.7.3 How to Cache Multi-Language Support Data

In addition to the instructions provided in Section 57.7.2, "How to Add Data to Cache", you must also perform the following steps to cache multi-language support (MLS) data. These steps are required because the shared application module and view object cache only stripes data by bind parameters. Therefore, you must build your MLS view objects for caching differently than other normal MLS view objects. For example, you must add bind parameters to the MLS cache view objects.

57.7.3.1 Creating ADF Business Components objects for shared MLS data

The procedure for creating ADF Business Components objects for shared MLS data varies depending on where the shared data requirement exists. The steps you follow are different for sharing data from the base table, from the translatable, or _TL, table or from both the base and the _TL table.

57.7.3.1.1 How to create objects if only the data from the base table needs to be shared
  1. Create an entity object on top of the base table if you need the change notification feature. This means that your data in cache is refreshed when there is a change in the underlying table. This is required because the database change notification feature doesn't work against database views.

  2. Create a view object on top of the _VL entity object if you do not require change notification. Otherwise, create a view object on top of the entity object created from the previous step

  3. Exclude all language dependent attributes from the _VL entity object.

57.7.3.1.2 How to create objects if only the data from the _TL table needs to be shared
  1. Create the entity object on top of the _TL table.

    This is required by MLS Framework. For more information, see Section 9.2, "Using Multi-Language Support Features."

  2. Create a view object on top of the _TL entity object.

  3. Create commonly used view criteria, with language being part of the criteria using a bind variable.

    Caution:

    The language must always be part of any view criteria. This is very important.
57.7.3.1.3 How to create objects if both the data from the base table and the _TL table needs to be shared
  1. Create the entity object on top of the _TL table.

    This is required by MLS Framework. For more information, see Section 9.2, "Using Multi-Language Support Features."

  2. Create an entity object on top of the base table if you need the change notification feature. This means that your data in cache is refreshed when there is a change in the underlying table. This is required because the database change notification feature does not work against database views.

    If you do not need the change notification feature, then you can use the existing _VL entity object, which should have been created already because it is required by MLS Framework.

  3. Create a view object to join the entity object created in the previous step (either a _VL entity object or an entity object based on the base table) and the _TL entity object. The view object should have all the language dependent attributes from the _VL entity object excluded, which allows the language dependent attribute to always come from the _TL entity object.

    Tip:

    This is important as it allows different users to see data for their language.
  4. Create commonly used view criteria, with language being part of the criteria using a bind variable.

    Caution:

    The language must always be part of any view criteria. This is very important.

57.7.3.2 Creating ADF Business Components Objects that Join to MLS tables

The procedure for creating ADF Business Components objects that join to MLS tables varies depending on where the data requirement exists. The steps you follow are different if the data is from the base table, from the translatable, or _TL, table, or from both the base and the _TL table.

57.7.3.2.1 How to create objects if only the data from the base table is required
  1. Create an entity object on top of the base table if you need the change notification feature. This means that your data in cache is refreshed when there is a change in the underlying table. This is required because the database change notification feature does not work against database views.

  2. Create a view object that joins to the _VL entity object if you do not need the change notification feature, or create one that joins to the entity object created in previous step if change notification feature is required.

  3. Exclude all the language dependent attributes from the _VL entity object.

57.7.3.2.2 How to create objects if only data from the _TL table is required
  1. Create a view object that joins to the _TL entity object.

  2. Create view criteria with language being part of the criteria using a bind variable.

    Caution:

    The language must always be part of any view criteria. This is very important.
57.7.3.2.3 How to create objects if data from both the base table and the _TL table is required
  1. Create an entity object on top of the base table if you need the change notification feature. This means that your data in cache is refreshed when there is a change in the underlying table. This is required because the database change notification feature does not work against database views.

  2. Create a view object that joins to the _VL entity object or the entity object created in the previous step, and the _TL entity object.

  3. Exclude all the language dependent attributes from the _VL entity object so that the language dependent attributes always come from the _TL entity object.

    Tip:

    This is important as it allows different users to see data for their language.
  4. Create commonly used view criteria, with language being part of the criteria using a bind variable.

    Caution:

    The language must always be part of any view criteria. This is very important.

57.7.4 How to Consume Cached Data

The most common approach for accessing the shared data is to create a view accessor. You can also instantiate a shared application module programmatically if your use case requires it.

57.7.4.1 Consuming Shared Data Using a View Accessor

Follow these steps to consume shared data using a view accessor:

  1. Identify the shared application module that contains the shared data.

  2. (Optional) Create view accessors on top of a shared view object.

    If the shared view object contains language specific attributes, make sure to include a view criteria that filters by language and bind the language to the current session language when defining your view accessor.

    For information about how to create view accessors, see the Accessing View Instances of the Shared Service section of the "Sharing Application Module View Instances" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

  3. (Optional) Build validators on top of the view accessors that you created in Step 2.

    This allows defaulting and derivation, and other business logic to utilize these view accessors.

  4. (Optional) Use the shared view object instead of using entity object as the validation target type for the key exist validator that validates shared data.

    Tip:

    If you use entity object target type, it does not use application-level cache.

57.7.4.2 Creating a shared application module programmatically

If you have an existing local application module, use the findOrCreateSharedApplicationModule method to create a shared application module. If you do not have a handle to an existing local application module, then use createRootApplicationModuleHandle from the oracle.jbo.client.Configuration class. Ensure that you release the application module after you are done, for example:

ApplicationModuleHandle handle =
Configuration.createRootApplicationModuleHandle("mypkg.AppModule", "AppModuleShared");
ApplicationModule sharedAM = handle.useApplicationModule();
...
Configuration.releaseRootApplicationModuleHandle(handle, false);

If you rely upon the database change notification feature to refresh your shared AM cache, then you also need to manually invoke the processChangeNotification method on the shared AM in order to get the latest data. For more information, see the "Sharing Application Module View Instances" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.7.5 What Happens at Runtime: When Another Service Accesses the Shared Application Module Cache

During runtime, only one instance of a shared application module is created in the application module pool. If there is an existing application module in the pool, then the existing application module instance is returned when you request a shared application module. For more information, see the "What Happens at Runtime: When Another Service Accesses the Shared Application Module Cache" section in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.8 Profiling and Tracing Oracle Fusion Applications

To monitor performance in Oracle Fusion Applications you can use the JDeveloper Profiler and capture SQL Trace for Oracle Fusion Applications. For detailed information about monitoring and debugging techniques, see the "Monitoring Oracle Fusion Middleware" chapter in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

57.8.1 How to Profile Oracle Fusion Applications with JDeveloper Profiler

The JDeveloper Profiler is used to provide information about the CPU, elapsed time, and memory metrics, as well as call counts. It can be very helpful when you are dealing with a performance issue or just trying to understand the performance characteristics of your code.

For more information about JDeveloper Profiler, consult the JDeveloper Help documentation.

Useful profiling modes are:

  • Sample CPU time with Collect Elapsed Time: Using this mode, you can find the methods using the most CPU. The Collect Elapsed Time option also shows the method taking the most time, including time spent in the database. This mode has low overhead and does not significantly slow down the application.

    Figure 57-10 Edit Run Configuration — Profiler: CPU

    Edit Run Configuration - Profiler: CPU

    If you find a method with a high elapsed time but low CPU time and that method includes a database call, this could indicate either a slow query or too many database roundtrips between the database and the middle-tier over a slow network. Look for methods with the highest exclusive CPU (sort on the CPUx field), and use the stack trace to determine where they are called from and if they can be optimized.

  • Memory Profiling: Using this mode, you can find out how much memory is allocated during the test.

  • Call Count Profiling: This is part of the CPU profiler and can be used to find out how many times each method is called.

    Caution:

    Call count profiling has very high overhead and therefore, you should increase Oracle JDeveloper starting memory before using it.

    To reduce resource consumption, you should set appropriate filters to include only the classes you are interested in.

57.9 Set up a Debug Breakpoint

If you are interested in where a certain method is called, you can set a breakpoint on that method and capture the stack trace. You can do this either interactively or preferably, you can set up a debug breakpoint at the target line and print the stack automatically.

To set up a debug breakpoint at the target line:

  1. Highlight your breakpoint in the Breakpoints page.

  2. Click Edit and select the Actions tab.

  3. Deselect the Halt Execution option and select the Log Breakpoint Occurrence and the Stack option. Selecting the Stack option gives you the stack trace, as shown in Figure 57-11.

    Figure 57-11 Edit Breakpoint

    Edit Breakpoint

Each time the breakpoint is hit, the stack is written to the console. To capture this, you must log the console output to a file.

To log the console output to a file:

  1. Go to Tools, Preferences and select the Environment: Log category.

  2. Select the Save Logs to File option and specify the Log directory, as shown in Figure 57-12.

    Figure 57-12 Preferences — Environment: Log

    Preferences - Environment: Log

    After running your project, you can find the console logged to a file in the specified directory.