Friday, May 22, 2009

Understanding Application Module Pooling Concepts and Configuration Parameters

Abstract

Oracle ADF provides a sophisticated and high-performance solution for managing pools of lightweight application module components. This article describes the pooling behavior and the different configuration parameters that allow you to control how the pools behaves at runtime.
NOTE:
This article is valid for ADF Business Components in JDeveloper 10g and beyond, as well as for Business Components for Java (BC4J) in JDeveloper 9.0.4 and earlier.
Overview

At its most simple, an application module pool is a collection of instances of a single application module type which are shared by multiple application clients. For example, the ADF Toy Store application has an application module named toystore.model.services.ToyStoreService. An application module pool supporting the Toy Store demo would have one or more instances of this application module component in it, based on the number of users that are visiting the site.

This pool of AM instances is shared by multiple browser clients whose typical "think time" between submitting web pages allows optimizing the number of AM components to be effectively smaller than the total number of "active" users working on the system. That is, twenty users shopping the web site from their browser might be able to be serviced by 5 or 10 application module instances instead of having as many AM instances as you have browser users.
Application module components can be used to support web application scenarios that are completely stateless, or they can be used to support a unit of work which spans multiple browser pages. Just imagine a scenario where an application user needs to accomplish the task of:

* Defining the details for a new warehouse
* Adding contact information for multiple contacts for this new warehouse
* Adding a number of initial inventory items to this new warehouse
Each of these steps might be accomplished using the popular "Step-by-Step" or "Wizard-Style" user interface that leads the user through a logical sequence of pages to complete the task, and allows them to iterate through the steps as many times as necessary to finish the application task. When the task is done, the user can commit everything or cancel the entire unit of work. Since this is a web application using the stateless HTTP protocol, implementing a sophisticated web application that builds up pending state over many web pages requires some state management strategy and software infrastructure to implement it. ADF application modules provides the ability to snapshot and reactivate their pending state to XML (stored on the file system or in the database), and the ADF application module pooling mechanism leverages this capability to deliver a "managed state" option to web application developers that simplifies building applications like the example just given.

As a performance optimization, when an instance of an AM is returned to the pool in "managed state" mode, the pool keeps track that the AM is referenced by that particular session. The AM instance is still in the pool and available for use, but it would prefer to be used by the same session that was using it last time because maintaining this so-called "session affinity" improves performance.

So, at any one moment in time, the instances of application modules in the pool are logically partitioned into three groups, reflecting their state:

1. Unconditionally available for use
2. Available for use, but referenced for session affinity reuse by an active user session XYZ
3. Unavailable, inasmuch as it's currently in use (at that very moment!) by some thread in the web container.

The next section describes the application module pool configuration parameters and how they affect the behavior of the pool.

Setting Pool Configuration Parameters

You control the runtime behavior of an application module pool by setting appropriate configuration parameters. The simplest way to assign values to these parameters is to use the application Configuration Manager. This dialog appears when you select an application module in the JDeveloper Application Navigator, and choose Configurations... from the right-mouse menu. While editing a configuration using this dialog.
TIP:

We'll explain in the Database Connection Pool Parameters section below why it is best practice to leave the Connection Pool parameters in this dialog at their defaults and to set your desired values for them globally using Java System Parameters.

Pooling and Scalability Tab of the Configuration Manager
Pooling and Scalability Tab of the Configuration Manager

The values that you supply through the Configuration Manager are saved in an XML file named bc4j.xcfg. All of the configurations for all of the application modules in a single Java package are saved in the same file. For example, if you have a EmployeeReviewModule and a BonusAssignment application module which both live in the com.yourcompany.hr package, then the configuration information for both of these modules will be saved in the bc4j.xcfg file in the ./com/yourcompany/hr/common directory in your project's source path. When your project is compiled, this bc4j.xcfg file gets copied to the same directory in your project's out path, so that the ADF framework can find it at runtime.

If you look inside the file, you'll notice that each named configuration is saved in an XML section in the file like this:

test.model.TestModule
LOCAL
scott
test.model.TestModule
Model


Note that child elements of the tag appear with tag names matching their property values. It's also important to understand that if a property is currently set to its runtime default value, then the Configuration Manager does not write the entry to the bc4j.xcfg file.

As an alternative to specifying configuration properties in the bc4j.xcfg file, you can also set Java VM system parameters with the same property names. These system parameters will be used only if a corresponding property does not exist in the relevant bc4j.xcfg file for the application module in question. In other words, configuration parameters that appear in the application module configuration take precedence over parameters of the same name supplied as Java system parameters.

You typically set Java system parameters using the -D command line flag to the Java VM like this:

java -Dproperty=value -jar yourserver.jar

Alternatively, your J2EE container probably has a section in its own configuration files where Java system parameters can be specified for use at J2EE container startup time.

TIP:

Many customers adopt the best practice technique of supplying site-specific default values for ADF configuration parameters as Java system parameters and then make sure that their bc4j.xcfg files do not include references to these parameters unless an application-module-specific exception to these global default values is required.

Using OC4J you can specify these parameters either as -D Java system parameters on the command line that starts OC4J, or provide them — one per line — in the oc4j.properties file and add the -properties oc4j.properties command line flag to OC4J at startup.


CAVEAT:

The values of Idle Instance Timeout, Pool Polling Interval settings for both the Application Pool and the database Connection Pool are displayed and edited in this dialog as a number of seconds, but are saved to the configuration file in milliseconds. If you provide a value for any of these four parameters as a Java System parameter — or if you hand-edit the bc4j.xcfg file — make sure to provide these time interval values in milliseconds!

AM State Snapshots and the Internal Connection

In order to manage application module pending work, the application module pool asks AM instances to "snapshot" their state to XML at different times. What is saved to the snapshot is not a copy of all data that's been queried by the view objects in the application module, but rather a kind of "redo log" of entity cache information about what pending entity object instances have been created, deleted, and modified, along with some view rowset iterator bookkeeping information for view objects with active rowsets.

If the value of the jbo.dofailover configuration parameter is true, then this XML snapshotting will happen each time the AM instance is released to the pool. You control this in the Configuration Manager by the checkbox labeled Failover Transaction State Upon Managed Release on the Pooling and Scalability tab. The default is to have this checkbox checked, meaning the parameter has a value of true. If the value is false, then an XML snapshot of pending work will only be taken on demand when an AM instance that was referenced by session "A" had to be handed out to another session "B" before session "A", that used it last and released it in managed state mode, came back to use it again. Those XML state snapshots are used by the pool to reinstate the pending work that your session had done up to the last time the AM instance was released to the pool in the case that your session cannot end up exploiting the session affinity optimization.

The setting of the jbo.passivationstore parameter controls whether the XML snapshots are written to files on disk (when the value is file) or to a BLOB column in a row in the PS_TXN table in the database (when the value is database). The default setting for this property is the string-value "null", which causes a database-based passivation scheme to be used against Oracle or DB2, and a file-based passivation scheme to be used otherwise.

While the file-based option is a little faster, unless your multiple application server instances share a file system, then the the database-backed passivation scheme is the most robust for application server-farm and failover scenarios.

If you use database passivation, then the framework will use a JDBC connection from a database connection pool for "internal connection" instances. The internal connection credentials are controlled by the value of the jbo.server.internal_connection configuration parameter. Its value can be a fully-qualified JDBC connection URL (e.g. jdbc:oracle:thin:statemgmtuser/password@penguin:1521:ORCL) or a JNDI name for a JDBC DataSource (e.g. java:comp/env/jdbc/AnotherConnectionDS). It is typically a different database account than your application data. Nothing prevents you from setting the internal connection to be using a database on a different machine as well, if desired.

How Many Pools are Created, and When?

There are two kinds of pools in use when running a typical ADF web application, Application Module pools and database connection pools. It's important to understand how many of each kind of pool your application will create, so let's explore both kinds in turn.

Application Module Pools

Application Module components can be used at runtime in two ways:

1. As a application module the client accesses directly, or
2. As a reusable component aggregated (or "nested") inside of another application module instance.

When a client accesses it directly, an application module is called a root application module. Clients access nested application modules indirectly as a part of their containing application module instance. It's possible, but not common, to use the same application module at runtime in both ways. The important point is that ADF only creates an application module pool for a root application module. Nested application modules are "along for the ride", so to speak, with the eventual root application module instance that contains them.

The basic rule is:

One application module pool is created for each root application module used by an ADF web application in each Java VM where a root application module of that type is used by the ADF controller layer.


NOTE:

While you can make direct use of the PoolMgr and ApplicationPool API's directly in your own web-tier infrastructure code, most customers use the default support provided by the ADF controller layer which handles interacting with the pool for them as described in the Lifecycle of a Web Page Request Using Struts and ADF section of the ADF Toy Store Demo whitepaper.

Database Connection Pools

ADF web applications always use a database connection pool, but which one they use for your application modules depends on whether your they define their connection using a:

* JDBC URL (e.g. jdbc:oracle:thin:@penguin:1521:ORCL), or a
* JNDI Name for a Datasource (e.g. java:comp/env/jdbc/YourConnectionDS)

If you supply a JDBC URL connection while configuring your application module — which happens when you select a JDeveloper named connection which encapsulates the JDBC URL and username information — then the ADF database connection pool will be used for managing the connection pool.

If instead you supply the JNDI name of a JDBC Datasource then the ADF database connection pool will not be used and the configuration parameters described below relating to the ADF database connection pool are not relevant.

NOTE:

To configure the database connection pool for JDBC Datasources looked-up by JNDI from your J2EE Web and/or EJB container, consult the documentation for your J2EE container to understand the pooling configuration options and how to set them.

Using ADF database connection pooling, we have the following basic rule:

One database connection pool is created for each unique pair, in each Java VM where a connection is requested by a root application used by the ADF controller layer.

Applying These Rules of Thumb to a Scenario

To better understand these rules of thumb, let's apply them to a simple scenario. We'll make the following assumptions:

* Your web application makes use of two application modules HRModule and PayablesModule.
* You have a CommonLOVModule containing a set of commonly used view objects to support list-of-values in your application, and that both HRModule and PayablesModule aggregate a nested instance of CommonLOVModule to access the common LOV view object's it contains.
* You have configured both HRModule and PayablesModule to use the same JDeveloper connection definition named appuser
* In both HRModule and PayablesModule you have configured jbo.passivationstore=database (the default) and configured the ADF "internal connection" (jbo.server.internal_connection) used for state management persistence to have the value of a fully-qualified JDBC URL that points to a different username than the appuser connection does.

Let's look at how many pools of which kinds are created for this application in both a single JVM and multiple JVM runtime scenario.

Single OracleAS Instance, Single OC4J Container, Single JVM

If we deploy this application to a single Oracle Application Server instance, configured with a single OC4J container having a single Java VM, there understandably only a single Java VM available to service the web requests coming from our application users.

Assuming that all the users are making use of web pages that access both the HRModule and the PayablesModule, this will give us:

* One AM pool for the HRModule root application module
* One AM pool for the PayablesModule root application module
* One DB connection pool for the appuser connection
* One DB connection pool for the JDBC URL supplied for the internal connection for state management.

This gives us a total of 2 AM pools and 2 database pools in this single Java VM.

NOTE:

There is no separate AM pool for the nested instances of the reusable CommonLOVModule. It goes along for the ride with instances of HRModule and PayablesModule in their respective AM pools.

Multiple OracleAS Instances, Single OC4J Container, Multiple JVMs

Let's consider next a deployment environment involving multiple Java VM's. Assume that we have installed Oracle Application Server 10g (version 9.0.4) onto two different physical machines, with a hardware load-balancer in front of it. On each of these two machines, imagine that the OracleAS instance is configured to have one OC4J container with two JVMs. As users of our application access the application, their requests end up getting "fanned out" across these two OracleAS instances, and within each OracleAS instance, across the two JVMs that its OC4J container has available.

Again assuming that all the users are making use of web pages that access both the HRModule and the PayablesModule, this will give us:

*

(1 HRModule Root AM) x (2 OracleAS Instances) x (2 OC4J JVMs each) =

Four AM pools for HRModule, one in each of 4 JVMs.
*

(1 PayablesModule Root AM) x (2 OracleAS Instances) x (2 OC4J JVMs each) =

Four AM pools for PayablesModule, one in each of 4 JVMs.
*

(1 appuser DB connection pool) x (2 OracleAS Instances) x (2 OC4J JVMs each) =

Four DB connection pools for appuser, one in each of 4 JVMs.
*

(1 internal connection JDBC URL DB connection pool) x (2 OracleAS Instances) x (2 OC4J JVMs each) =

Four DB connection pools for the internal connection JDBC URL, one in each of 4 JVMs.

This gives us a total of 8 AM pools and 8 DB connection pools spread across 4 JVMs.

As we begin to explore the configuration parameters for the AM pools in the next section, keep in mind that they apply to a given AM pool for a given application module in a single JVM. As the load balancing spreads user request across the multiple JVMs where ADF is running, each individual AM pool in each JVM will have to support one Nth of the user load — where N is number of JVMs available to service those user requests. The appropriate values of the AM and DB connection pools need to be set with the number of Java VM's in mind.

Application Module Pool Parameters

The application module pool configuration parameters fall into two logical categories relating to pool sizing and pool cleanup behavior.

Pool Sizing Parameters

Table 1 lists the application module configuration parameters that affect the sizing of the application module pool.
Table 1: Application Module Pool Sizing Configuration Parameters
Pool Parameter Configuration Parameter Name Description
Initial Pool Size jbo.ampool.initpoolsize

The number of AM instances to created when the pool is initialized.

The default is 0 (zero) instances.
Maximum Pool Size jbo.ampool.maxpoolsize

The maximum number of AM instances that the pool can allocate.

The pool will never create more AM instances than this limit imposes. The default is 5000 instances.
Referenced Pool Size jbo.recyclethreshold

The maximum number of AM instances in the pool that attempt to preserve session affinity for the next request made by the session which used them last before releasing them to the pool in managed-state mode.

The referenced pool size should always be less than or equal to the maximum pool size. The default is to allow 10 available instances to try and remain "loyal" to the affinity they have with the most recent session that released them in managed state mode.

Pool Cleanup Parameters

A single "application module pool monitor" per Java VM runs in a background thread and wakes up every so often to do resource reclamation. Table 2 lists the parameters that affect how resources are reclaimed when the pool monitor does one of its resource cleanup passes.

TIP:

Since there is only a single application monitor pool monitor per Java VM, the value that will effectively be used for the AM pool monitor polling interval will be the value found in the AM configuration read by the first AM pool that gets created. To make sure this value is set in a predictable way, it is best practice for all application modules to use the same Pool Polling Interval value.

Table 2: Application Module Resource Management Configuration Parameters
Pool Parameter Configuration Parameter Name Description
Pool Polling Interval jbo.ampool.monitorsleepinterval

The length of time in milliseconds between pool resource cleanup.

While the number of AM instances in the pool will never exceed the maximum pool size, available instances which are candidates for getting removed from the pool do not get "cleaned up" until the next time the AM pool monitor wakes up to do its job. The default is to have the AM pool monitor wake up every 600000 milliseconds (which is 600 seconds, or ten minutes).

Note: Value in the configuration UI is in shown/edited in seconds, but saved in milliseconds.
Maximum Available Size jbo.ampool.maxavailablesize

The ideal maximum number of AM instances in the pool when not under abnormal load.

When the pool monitor wakes up to do resource cleanup, it will try to remove available AM instances to bring the total number of available instances down to this ideal maximum. Instances that have been not been used for a period longer than the idle instance timeout will always get cleaned up at this time, then additional available instances will be removed if necessary to bring the number of available instances down to this size. The default maximum available size is 25 instances.
Minimum Available Size jbo.ampool.minavailablesize

The minimum number of available AM instances that the pool monitor should leave in the pool during a resource cleanup operation. Set to zero (0) if you want the pool to shrink to contain no instances when all instances have been idle for longer than the idle timeout.

The default is 5 instances.
Idle Instance Timeout jbo.ampool.maxinactiveage

The number of milliseconds after which to consider an inactive AM instance in the pool as a candidate for removal during the next resource cleanup.

The default is 600000 milliseconds of idle time (which is 600 seconds, or ten minutes).

Note: Value in the configuration UI is in shown/edited in seconds, but saved in milliseconds.
Maximum Instance Time to Live jbo.ampool.timetolive

The number of milliseconds after which to consider an AM instance in the pool as a candidate for removal during the next resource cleanup regardless of whether it would bring the number of instances in the pool below minavailablesize.

The default is 3600000 milliseconds of total time to live (which is 3600 seconds, or one hour)

Note: This property only appears in the Properties panel, not on the Pooling and Scalability Tab Database Connection Pool Parameters

If you are using a JDBC URL for your connection information so that the ADF database connection pool is used, then configuration parameters listed in Table 3 can be used to tune the behavior of the database connection pool. A single "database connection pool monitor" per Java VM runs in a background thread and wakes up every so often to do resource reclamation. The parameters in Table 2 include the ones that affect how resources are reclaimed when the pool monitor does one of its resource cleanup passes.
TIP:

Since the tuning parameters for all ADF database connection pools — regardless of value — will be set based on the parameters found in the configuration for the first AM pool that is created. To insure the most predictable behavior, it is best practice to leave the values of the parameters in the Connnection Pooling section of the Pooling and Scalability tab at their default values — so that no entry for them is written into the bc4j.xcfg file — and to instead set the desired values for the database connection pooling tuning parameters as Java System Parameters in your J2EE container.
In fact, to a Bug# 3678499 in JDeveloper 10g (version 9.0.5.x) database connection pool tuning parameters that appear in your application module configuration files (bc4j.xcfg) are inadvertently ignored and default settings are used, including the default setting of jbo.poolminavailablesize controlling the minimum number of instances to always remain in the pool. So in the 9.0.5.x release, the only way to set non-default database pooling parameters is to set them using Java System parameters.
Table 3: Database Connection Pool Parameters
Pool Parameter Configuration Parameter Name Description
Initial Pool Size jbo.initpoolsize

The number of JDBC connection instances to created when the pool is initialized

The default is an initial size of 0 instances.
Maximum Pool Size jbo.maxpoolsize

The maximum number of JDBC connection instances that the pool can allocate.

The pool will never create more JDBC connections than this imposes. The default is 5000 instances.

Note: Value in the configuration UI is in shown/edited in seconds, but saved in milliseconds.
Pool Polling Interval jbo.poolmonitorsleepinterval

The length of time in milliseconds between pool resource cleanup.

While the number of JDBC connection instances in the pool will never exceed the maximum pool size, available instances which are candidates for getting removed from the pool do not get "cleaned up" until the next time the JDBC connection pool monitor wakes up to do its job. The default is 600000 milliseconds of idle time (which is 600 seconds, or ten minutes).

Note: Value in the configuration UI is in shown/edited in seconds, but saved in milliseconds.
Maximum Available Size jbo.poolmaxavailablesize

The ideal maximum number of JDBC connection instances in the pool when not under abnormal load.

When the pool monitor wakes up to do resource cleanup, it will try to remove available JDBC connection instances to bring the total number of available instances down to this ideal maximum. Instances that have been not been used for a period longer than the idle instance timeout will always get cleaned up at this time, then additional available instances will be removed if necessary to bring the number of available instances down to this size. The default is an ideal maximum of 25 instances (when not under load).
Minimum Available Size jbo.poolminavailablesize

The minimum number of available JDBC connection instances that the pool monitor should leave in the pool during a resource cleanup operation. Set to zero (0) if you want the pool to shrink to contain no instances when all instances have been idle for longer than the idle timeout.

The default is to not let the minimum available size drop below 5 instances.
Idle Instance Timeout jbo.poolmaxinactiveage

The number of seconds after which to consider an inactive JDBC connection instance in the pool as a candidate for removal during the next resource cleanup.

The default is 600000 milliseconds of idle time (which is 600 seconds, or ten minutes).

Note: Value in the configuration UI is in shown/edited in seconds, but saved in milliseconds.
Maximum Instance Time to Live jbo.pooltimetolive

The number of milliseconds after which to consider an connection instance in the pool as a candidate for removal during the next resource cleanup regardless of whether it would bring the number of instances in the pool below minavailablesize.

The default is 3600000 milliseconds of total time to live (which is 3600 seconds, or one hour)
Note: This property only appears in the Properties panel, not on the Pooling and Scalability Tab

Notice that since the BC4J database connection pool does not implement the heuristic of session affinity, there is no configuration parameter for the database connection pool which controls the referenced pool size.

You should take care not to configure the jbo.ampool.monitorsleepinterval (for the AM pools) or the jbo.poolmonitorsleepinterval (for the DB pools) to be too short of a time period because the chance exists — with a large number of AM pools to cleanup — that your next pool monitor "wakeup" might occur while your previous cleanup-cycle is still going on. The default of 10 minutes (600000 milliseconds) is reasonable. Setting it to something like 10 seconds (10000 milliseconds) might cause trouble. Forewarned is forearmed.

Understanding How Database and Application Module Pools Cooperate

How ADF application module pools use the database connection pool depends on the setting of the jbo.doconnectionpooling AM configuration parameter. you set this parameter using the checkbox labelled Disconnect Application Module Upon Release.
NOTE:

The notion of disconnecting the application module upon release to the pool better captures what the actual feature is doing than the related configuration parameter name (jbo.doconnectionpooling) does. As we'll see the setting of jbo.doconnectionpooling=false does not mean that there is no database connection pooling happening. What it means is that the AM is not disconnected from its JDBC connection upon checkin back to the AM pool.

If jbo.doconnectionpooling=false, which is the default, then when an AM instance is created in any pool it acquires a JDBC connection from the appropriate connection pool (based on the JDBC URL in the ADF case, or from the underlying JDBC datasource implementation's pool in the case of a JNDI datasource name). That AM instance holds onto the JDBC connection object that it acquired from the pool until the AM instance is removed from the AM pool. During its lifetime, that AM instance may service many different users, and ADF worries about issuing rollbacks on the database connection so that different users don't end up getting pending database state confused. This provides the best performance in general because, by holding onto the JDBC connection, it allows each AM instance to keep its JDBC PreparedStatements's open and usable across subsequent accesses by clients.

If jbo.doconnectionpooling=true, then each time a user session finishes using an AM (typically at the end of each HTTP request), the AM instance disassociates itself with the JDBC connection it was using on that request and it returns it to the JDBC connection pool. The next time that AM instance is used by a user session, it will reacquire a JDBC connection from the JDBC connection pool and use it for the span of time that that AM is checked out of the AM pool (again, typically the span of one HTTP request). Since the AM instance "unplugs" itself from the JDBC connection object used to create the PreparedStatements it might have used during the servicing of the current HTTP request, those PreparedStatements are no longer usable on the next HTTP request because they are only valid in the context of the Connection object in which they were created. So, using the connection pooling mode turned "on" like this, the tradeoff is a slightly more JDBC setup/overhead each time in return for using a smaller number of overall database connections.
The key difference is seen when many AM pools are all using the same underlying database user for their application connection.

* If 50 different AM pools each have even just a single AM instance in them, with jbo.doconnectionpooling=false there will be 50 JDBC application connections in use. If the AM pooling parameters are set such that the AM pools are allowed to shrink to 0 instances after an appropriate instance idle timeout by setting jbo.ampool.minavailablesize=0, then when the AM is removed from its pool, it will put back the connection its holding onto.
* In contrast, if 50 different AM pools each have a single AM instance and jbo.doconnectionpooling=true, then the amount of JDBC connections in use will depend on how many of those AM's are simultaneously being used by different clients. If an AM instance is in the pool and is not currently being used by a user session, then with jbo.doconnectionpooling=true it will have released its JDBC connection back to the connection pool and while the AM instance is sitting there waiting for either another user to need it again, or to eventually be cleaned up by the AM pool monitor, it will not be "hanging on" to a JDBC connection.

For highest performance, we recommend not disconnecting the application module instance from its database connection on each check in to the AM pool. Accordingly, the default setting of the jbo.doconnectionpooling configuration parameter is false. The pooling of application module instances is already an effective way to optimize resource usage, and there are runtime efficiencies that the ADF framework can gain if we do not have to disconnect AM instances from their associated JDBC connection after each release to the pool. Effectively, by pooling the application modules which are related one-to-one with a JDBC connection, you are already achieving a pooling of database connections that is optimal for most web applications.

In contrast to our default recommendation, one situation in which it might be opportune to use database connection pooling is when you have a large number of application module pools all needing to use database connections from the same underlying application user at the database level. In this case, the many application module pools can perhaps economize on the total overall database sessions by sharing a single, underlying database connection pool of JDBC connections, albeit at a loss of efficiency of each one. This choice would be favored only if total overall database sessions is of maximum priority.
CAVEAT:
Disconnecting the AM from it's JDBC connection at each AM checkin is fundamentally incompatible with the use of the ADF Business Components feature to call postChanges() — without committing — on the current transaction. Having this feature work correctly relies on allowing the AM to retain its database connection so that interim posted database changes — which have yet to be committed — are maintained by the database. The use of postChanges() is often a sign that you are not aware that the ADF pending state management mechanism will automatically keep track of pending new, modified, and deleted rows for you across web page requests without needing to post them to the database until the final transaction commit.

0 comments: