Oracle® Fusion Applications Developer's Guide 11g Release 6 (11.1.6) Part Number E15524-11 |
|
|
PDF · Mobi · ePub |
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:
Section 58.1, "Introduction to Improving the Performance of Applications"
Section 58.4, "SOA Guidelines for Human Workflow and Approval Management Extensions"
Section 58.5, "Oracle Fusion Middleware Extensions for Applications Guidelines"
Section 58.8, "Profiling and Tracing Oracle Fusion 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.
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.
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 (Oracle Fusion Applications Edition).
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 58-1. You should also set the Threshold property to 1, which is important for _TL
multi language entities.
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.
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.
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.
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.
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
.
The same principle applies for view accessor rowsets. If your view accessor returns more than one row, then every time you start fetching rows from the view accessor with a different bind value, a new row set is created, along with a new underlying database cursor to execute the query. If you do not need all the rows, then you should configure the MaxFetchSize
setting on the view accessor to include only the number of rows you need. Then Oracle ADF closes the database cursor immediately upon fetching all the rows up to MaxFetchSize
. Alternatively, for cases where you expect to execute the view accessor several times with different bind values, in a single request, you should consider calling closeRowSet
on the view accessor rowset explicitly.
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 58-2), you should profile your flow to make sure you would indeed get a noticeable benefit.
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 58-3 illustrates an example profiler output showing addRowSet
being expensive.
Before you decide to retain association accessor, you should try the guideline Section 58.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 (Oracle Fusion Applications Edition).
When working with view objects, consider the following suggestions for improving performance.
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 58-4.
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.
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.
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 (Oracle Fusion Applications Edition).
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 (Oracle Fusion Applications Edition).
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.
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.
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
.
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
.
If your view object is a declarative-mode view object that is accessed using view accessor, you may still be able to specify which attributes are accessed. If the code looks like the following example, the first line calls the accessor getter. The query is not executed until hasNext()
is called on the last line. Therefore, you can use selectAttributeDefs
to control which attributes are select after calling the getter and before calling hasNext
.
RowSet rowSet = this.getPartySitePartySiteUseVVO(); RowSetIterator iterator = rowSet.createRowSetIterator("sellToUseIterator"); iterator.reset(); Row row = null; if (iterator.hasNext()){
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. However, ensure that you include sufficient attributes. If your view object is updateable, comply with File.AdfModel.112
.
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 58-5.
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.
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 58-1.
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)
.
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.
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.
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.
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.
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.
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.
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.
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.
When working with application modules, consider the following suggestions for improving performance.
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.
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 58-6 illustrates the application module position in the Oracle ADF applications architecture.
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:
Launch Oracle JDeveloper.
Right-click Application Module and select Configurations.
Click Edit, and select the Pooling and Scalability tab. See Figure 58-7.
Deselect Enable Application Module Pooling.
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 (Oracle Fusion Applications Edition).
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 (Oracle Fusion Applications Edition).
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 58-8.
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 58-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 (Oracle Fusion Applications Edition).
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 (Oracle Fusion Applications Edition).
The following is sample code from the Pathfinder Application to passivate and activate the UserLoginPrincipal
object. Example 58-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 58-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; } } } } }
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 58-1 illustrates application module release mode comparisons.
Table 58-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 |
In stateful mode, it is recommended that the Business Components property |
In release mode, you can reliably use pessimistic locking and may set the property |
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 (Oracle Fusion Applications Edition).
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.
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.
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.
When working with services, consider the following suggestions for improving performance.
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 58-3 is sample code showing how to create a find criteria.
Example 58-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 ###"); .......
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.
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.
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.
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.
When working with ADF ViewController components, consider the following suggestions for improving performance.
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.
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.
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.
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.
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 58-9 shows how the JavaServer Faces (JSF) and Oracle ADF phases integrate in the lifecycle of a page request.
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 (Oracle Fusion Applications Edition).
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.
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 58.2.3.2, "Make Application Code Passivation-Safe."
Example 58-4 shows how to use HTTPSession
cache in a backing bean.
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.
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.
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 58-5 is an example for panelAccordion
. (Note the use of $
instead of #
).
Note:
Using this technique is not recommended. For other techniques, see Section 58.3.1.20, Section 58.3.1.21, and Section 58.3.1.22.
Example 58-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 58-6 shows how to use this technique with lazy popups.
Example 58-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>
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.
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.
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
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.
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.
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.
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 (Oracle Fusion Applications Edition).
If you use the same taskflow multiple times on a page, and you are using shared data control scope, by default it may not work because the data controls will show the same data. To address this, instead of switching to isolated data control scope, follow the approach described in https://blogs.oracle.com/groundside/entry/maintaining_row_currency_separation_in
.
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.
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.
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:
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.)
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.
This recommendation is similar to Section 58.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.
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"/>
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; }
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.
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.
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.
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.
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.
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.
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.
When working with application modules, consider these best practices related to using a nested service and releasing application modules returned from getInstance
calls.
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.
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 58-7.
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}"
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.
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.
When working with Java, consider these best practices related to Strings and StringBuilder, Collections, Synchronization, as well as other Java features.
When working with Strings
and StringBuilder
, consider the following suggestions for improving performance.
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 58-8 uses the hashCode
implementation and requires String concatenation on every call.
Example 58-8 Hashcode with String Concatenation
public int hashCode() { … … … h = new StringBuffer(len).append(resp).append(rapl).toString().hashCode(); return h; }
Example 58-9 does not use 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.
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.
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.
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.
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.
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.
When working with Java features, you should consider the following:
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 58-10 shows how the compiled code basically creates a new Integer object based on the int value:
Example 58-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.
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
.
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.
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();
To avoid memory leaks, closed unused JDBC statements. Example 58-11 depicts a statement leak in java code.
Example 58-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 58-12.
Example 58-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 58-13.
Example 58-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()
.
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)
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.
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.
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:
Create a view object or identify an existing view object to store the cached data.
(Optional) Create commonly used view criteria for the shared view object.
(Optional) Configure the shared view object's property, such as time to live, and so on.
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 (Oracle Fusion Applications Edition).
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 58-14.
Example 58-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.
In addition to the instructions provided in Section 58.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.
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.
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.
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
Exclude all language dependent attributes from the _VL
entity object.
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."
Create a view object on top of the _TL
entity object.
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.
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."
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.
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.
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.
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.
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.
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.
Exclude all the language dependent attributes from the _VL
entity object.
Create a view object that joins to the _TL
entity object.
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.
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.
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.
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.
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.
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.
Follow these steps to consume shared data using a view accessor:
Identify the shared application module that contains the shared data.
(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 (Oracle Fusion Applications Edition).
(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.
(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.
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 (Oracle Fusion Applications Edition).
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 (Oracle Fusion Applications Edition).
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 (Oracle Fusion Applications Edition).
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.
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.
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:
Highlight your breakpoint in the Breakpoints page.
Click Edit and select the Actions tab.
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 58-11.
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:
Go to Tools, Preferences and select the Environment: Log category.
Select the Save Logs to File option and specify the Log directory, as shown in Figure 58-12.
After running your project, you can find the console logged to a file in the specified directory.