Oracle® Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework 11g Release 2 (11.1.2.1.0) Part Number E16182-02 |
|
|
PDF · Mobi · ePub |
This chapter describes the Fusion web application state management facilities and how to use them.
This chapter includes the following sections:
Section 43.1, "Understanding Why State Management is Necessary"
Section 43.2, "Introduction to Fusion Web Application State Management"
Section 43.4, "Setting the Application Module Release Level at Runtime"
Section 43.5, "What Model State Is Saved and When It Is Cleaned Up"
Section 43.9, "Using State Management for Middle-Tier Savepoints"
Section 43.10, "Testing to Ensure Your Application Module is Activation-Safe"
Most real-world business applications need to support multi-step user tasks. Modern sites tend to use a step-by-step style user interface to guide the end user through a logical sequence of pages to complete these tasks. When the task is done, the user can save or cancel everything as a unit. However, the HTTP protocol is built on the concept of individual, stateless requests, so the responsibility of grouping a sequence of user actions into a logical, stateful unit of work falls to the application developer.
In a typical search-then-edit scenario, the end user searches to find an appropriate row to update, then may open several different pages of related master/detail information to make edits before deciding to save or cancel his work. Consider another scenario where the end user wants to book a vacation online. The process may involve the end user's entering details about:
One or more flight segments that comprise the journey
One or more passengers taking the trip
Seat selections and meal preferences
One or more hotel rooms in different cities
Car they will rent
Along the way, the user might decide to complete the transaction, save the reservation for finishing later, or abandon the whole thing.
It's clear these scenarios involve a logical unit of work that spans multiple web pages. You've seen in previous chapters how to use JDeveloper's JSF page navigation diagram to design the page flow for these use cases, but that is only part of the puzzle. The pending changes the end user makes to business domain objects along the way — Trip
, Flight
, Passenger
, Seat
, HotelRoom
, Auto
, etc. — represent the in-progress state of the application for each end user. Along with this, other types of "bookkeeping" information about selections made in previous steps comprise the complete picture of the application state.
While it may be easy to imagine these multi-step scenarios, implementing them in web applications is complicated by the stateless nature of HTTP, the hypertext transfer protocol. Figure 43-1 illustrates how an end user's visit to a site comprises a series of HTTP request/response pairs. However, HTTP affords a web server no way to distinguish one user's request from another user's, or to differentiate between a single user's first request and any subsequent requests he makes while interacting with the site. The server gets each request from any user always as if it were the first (and only) one they make.
As shown in Figure 43-2, the technique used to recognize an ongoing sequence of requests from the same end user over the stateless HTTP protocol involves a unique identifier called a cookie. A cookie is a name/value pair that is sent in the header information of each HTTP request the user makes to a site. On the initial request made by a user, the cookie is not part of the request. The server uses the absence of the cookie to detect the start of a user's session of interactions with the site, and it returns a unique identifier to the browser that represents this session for this user. In practice, the cookie value is a long string of letters and numbers, but for the simplicity of the illustration, assume that the unique identifier is a letter like "A" or "Z" that corresponds to different users using the site.
Web browsers support a standard way of recognizing the cookie returned by the server that allows the browser to identify the following:
the site that sent the cookie
how long it should remember the cookie value
On each subsequent request made by that user, until the cookie expires, the browser sends the cookie along in the header of the request. The server uses the value of the cookie to distinguish between requests made by different users.
Cookies can be set to live beyond a single browser session so that they might expire in a week, a month, or a year from when they were first created, while a session cookie expires when you close your browser.
Java EE-compliant web servers provide a standard server-side facility called the HttpSession
that allows a web application to store Java objects related to a particular user's session as named attribute/value pairs. An object placed in this session Map
on one request can be retrieved by the application while handling a subsequent request during the same session.
The session remains active while the user continues to send new requests within the timeframe configured by the <session-timeout>
element in the web.xml
file. The default session length is 35 minutes.
The HttpSession
facility is an ingredient in most application state management strategies, but it can present performance and reliability problems if not used judiciously. First, because the session-scope Java objects you create are held in the memory of the Java EE web server, the objects in the HTTP session are lost if the server should fail.
As shown in Figure 43-3, one way to improve the reliability is to configure multiple Java EE servers in a cluster. By doing this, the Java EE application server replicates the objects in the HTTP session for each user across multiple servers in the cluster so that if one server goes down, the objects exist in the memory of the other servers in the cluster that can continue to handle the users requests. Since the cluster comprises separate servers, replicating the HTTP session contents among them involves broadcasting the changes made to HTTP session objects over the network.
You can begin to see some of the performance implications of overusing the HTTP session:
The more active users, the more HTTP sessions will be created on the server.
The more objects stored in each HTTP session, the more memory you will need. Note that the memory is not reclaimed when the user becomes inactive; this only happens with a session timeout or an explicit session invalidation. Session invalidations don't always happen because users don't always logout.
In a cluster, the more objects in each HTTP session that change, the more network traffic will be generated to replicate the changed objects to other servers in the cluster.
At the outset, it would seem that keeping the number of objects stored in the session to a minimum addresses the problem. However, this implies leveraging an alternative mechanism for temporary storage for each user's pending application state. The most popular alternatives involve saving the application state to the database between requests or to a file of some kind on a shared file system.
Of course, this is easier said than done. A possible approach involves eagerly saving the pending changes to your underlying database tables and committing the transaction at the end of each HTTP request. But this idea has two key drawbacks:
Your database constraints might fail.
At any given step of the multi-step process, the information may only be partially complete, and this could cause errors at the database level when trying to save the changes.
You complicate rolling back the changes.
Cancelling the logical of unit of work would involve carefully deleting all of the eagerly-committed rows in possible multiple tables.
These limitations have led developers in the past to invent solutions involving a "shadow" set of database tables with no constraints and with all of the column types defined as character-based. Using such a solution becomes very complex very quickly. Ultimately, you will conclude that you need some kind of generic application state management facility to address these issues in a more generic and workable way. The solution comes in the form of ADF Business Components, which implements this for you out of the box.
State management enables you to easily create web applications that support multi-step use cases without falling prey to the memory, reliability, or implementation complexity problems described in Section 43.1, "Understanding Why State Management is Necessary."
Application state management is provided at two levels, by the Save For Later feature in a task flow, and application module state management in the model layer.
Save For Later is activated at the controller layer and automatically saves a "snapshot" of the current UI and controller states, and delegates to the model layer to passivate (save) its state as well.
If you are not using ADF data controls, you can still use application module state management alone, but since this will save only the model state, this is an outside case for most applications.
Save for Later saves an incomplete transaction without enforcing validation rules or submitting the data. The end user can resume working on the same transaction later with the same data that was originally saved when the application was exited.
Your ADF Business Components-based application automatically manages the application state of each user session. This provides the simplicity of a stateful programming model that you are accustomed to in previous 4GL tools, yet implemented in a way that delivers scalability nearing that of a purely stateless application. Understanding what happens behind the scenes is essential to make the most efficient use of this important feature.
You can use application module components to implement completely stateless applications or to support a unit of work that spans multiple browser pages. Figure 43-4 illustrates the basic architecture of the state management facility to support these multi-step scenarios. An application module supports passivating (storing) its pending transaction state to an XML document, which is stored in the database in a single, generic table, keyed by a unique passivation snapshot ID. It also supports the reverse operation of activating pending transaction state from one of these saved XML snapshots. This passivation and activation is performed automatically by the application module pool when needed.
The ADF binding context is the one object that lives in the HttpSession
for each end user. It holds references to lightweight application module data control objects that manage acquiring an application module instance from the pool during the request (when the data control is accessed) and releasing it to the pool at the end of each request. The data control holds a reference to the ADF session cookie that identifies the user session. In particular, business domain objects created or modified in the pending transaction are not saved in the HttpSession
using this approach. This minimizes both the session memory required per user and eliminates the network traffic related to session replication if the servers are configured in a cluster.
For improved reliability, serialize your session objects. Objects stored in distributed sessions need to implement the java.io.Serializable
interface. Implementing this interface ensures the data can be transported over-the-wire to each server instance in the cluster. Use a custom method like the addObjectToSession(String key, Serializable value)
method, instead of the default HttpSession.setAttribute (String key, Object value)
method when adding session data. The distinction is, if you were to call the addObjectToSession()
method with a non-serializable object, you would see a compile-time error. If you were to try to replicate a session object that had non-serializable objects placed into session with the put()
method, you would see a runtime error and potentially, a broken user experience.
Additionally, if you have multiple application servers and you enable the optional ADF Business Components failover support (explained in Section 43.2.2.2, "How Passivation Changes When Optional Failover Mode is Enabled"), then subsequent end-user requests can be handled by any server in your server farm or cluster. The session cookie can reactivate the pending application state from the database-backed XML snapshot if required, regardless of which server handles the request.
To better understand when the automatic passivation and activation of application module state occurs, consider the following simple case:
At the beginning of an HTTP request, the application module data control handles the beginRequest
event by checking out an application module instance from the pool.
The application module pool returns an unreferenced instance. An unreferenced application module is one that is not currently managing the pending state for any other user session.
At the end of the request, the application module data control handles the endRequest
event by checking the application module instance back into the pool in "managed state" mode.
That application module instance is now referenced by the data control that just used it. And the application module instance is an object that still contains pending transaction state made by the data control (that is, entity object and view object caches; updates made but not committed; and cursor states), stored in memory. As you'll see below, it's not dedicated to this data control, just referenced by it.
On a subsequent request, the same data control — identified by its SessionCookie
— checks out an application module instance again.
Due to the "stateless with user affinity" algorithm the pool uses, you might assume that the pool returns the exact same application module instance, with the state still there in memory. (To understand this algorithm, read Section 44.1, "About Application Module Pooling" and the discussion of Referenced Pool Size in Section 44.2.7.2, "Pool Sizing Parameters.")
Sometimes due to a high number of users simultaneously accessing the site, application module instances must be sequentially reused by different user sessions. In this case, the application pool must recycle a currently referenced application module instance for use by another session, as follows:
The application module data control for User A's session checks an application module instance into the application pool at the end of a request. Assume this instance is named AM1
.
The application module data control for User Z's new session requests an application module instance from the pool for the first time, but there are no unreferenced instances available. The application module pool then:
Passivates the state of instance AM1
to the database.
Resets the state of AM1
in preparation to be used by another session.
Returns the AM1
instance to User Z's data control.
On a subsequent request, the application module data control for User A's session requests an application module instance from the pool. The application module pool then:
Obtains an unreference instance.
This could be instance AM1
, obtained by following the same steps as in (2) above, or another AM2
instance if it had become unreferenced in the meantime.
Activates the appropriate pending state for User A from the database.
Returns the application module instance to User A's data control.
The process of passivation, activation, and recycling allows the state referenced by the data control to be preserved across requests without requiring a dedicated application module instance for each data control. Both browser users in the above scenario are carrying on an application transaction that spans multiple HTTP requests, but the end users are unaware whether the passivation and activation is occurring in the background. They just continue to see the pending changes. In the process, the pending changes never need to be saved into the underlying application database tables until the end user is ready to commit the logical unit of work.
Note that this keeps the session memory footprint low because the only business component objects that are directly referenced by the session (and are replicable) are the data control and the session cookie.
The application module pool makes a best effort to keep an application module instance "sticky" to the current data control whose pending state it is managing. This is known as maintaining user session affinity. The best performance is achieved if a data control continues to use exactly the same application module instance on each request, since this avoids any overhead involved in reactivating the pending state from a persisted snapshot.
The jbo.dofailover
parameter controls when and how often passivation occurs. You can set this parameter in your application module configuration on the Pooling and Scalability tab of the Business Components Configuration dialog. When the failover feature is disabled, which it is by default, then application module pending state will only be passivated on demand when it must be. This occurs just before the pool determines it must hand out a currently-referenced application module instance to a different data control.
The passivation activity (and the corresponding wait) occurs only if failover is disabled and session replication is disabled. If either failover or session replication is enabled, then the recycling thread will reuse the snapshot that was already acquired to support the failover.
Note:
Passivation can also occur when an application module is timed out. For more information about application pool removal algorithms (such asjbo.ampool.timetolive
), see Section 44.2.7.3, "Pool Cleanup Parameters."In contrast, with the failover feature turned on, the application module's pending state is passivated every time it is checked back into application module pool. This provides the most pessimistic protection against application server failure. The application module instances' state is always saved and may be activated by any application module instance at any time. Of course, this capability comes at expense of the additional overhead of eager passivation on each request.
When failover is turned on, a failure can occur when Oracle WebLogic Server is configured to forcibly release connections back into the pool. A failure of this type produces a SQLException
(Connection has already been closed.) that is saved to the server log. The exception is not reported through the user interface. To ensure that state passivation occurs and users' changes are saved, the server administrator should set an appropriate value for the weblogic-application.xml
deployment descriptor parameter inactive-connection-timeout-seconds
on the <connection-check-params>
pool params element. Setting the deployment descriptor parameter to several minutes, in most cases, should avoid forcing the inactive connection timeout and the resulting passivation failure. Adjust the setting as needed for your environment.
Best Practice:
By default, the failover feature is disabled (jbo.dofailover=false
) as a performance optimization when there is only one web server instance configured, by reducing the need for passivation and activation. This allows for application module affinity to a specific user session.
For high availability, enable the failover feature (jbo.dofailover=true
) to ensure that more application modules are readily available, thereby increasing scalability. In this mode, passivation occurs at the end of every request.
To understand application module pooling and the "stateless with affinity" algorithm, read Section 44.1, "About Application Module Pooling" and the discussion of Referenced Pool Size in Section 44.2.7.2, "Pool Sizing Parameters."
Note:
When running or debugging an application that uses failover support within the JDeveloper environment, you are frequently starting and stopping the application server. The ADF failover mechanism has no way of knowing whether you stopped the server to simulate an application server failure, or whether you stopped it because you want to retest something from scratch in a fresh server instance. If you intend to do the latter, exit out of your browser before restarting the application on the server. This eliminates the chance that you will be confused by the correct functioning of the failover mechanism when you didn't intend to be testing that aspect of your application.When a data control handles the endRequest
notification indicating the processing for the current HTTP request has completed, it releases the application module instance by checking it back into the application module pool. The application module pool manages instances and performs state management tasks (or not) based on the release level you use when returning the instance to the pool.
There are three release levels used for returning an instance of an application module to a pool:
Managed - This is the default level, where the application module pool prefers to keep the same application module instance for the same data control, but may release an instance if necessary.
Unmanaged - No state needs to be preserved beyond the current request.
Reserved - A one-to-one relationship is preserved between an application module instance and a data control.
Caution:
In general, it is strongly recommended never to use Reserved release level. You would normally avoid using this mode because the data control to application module correlation becomes one to one, the scalability of the application reduces very sharply, and so does reliability of the application.This is the default release level and implies that application module's state is relevant and has to be preserved for this data control to span over several HTTP requests. Managed level does not guarantee that for the next request this data control will receive the same physical application module instance, but it does guarantees that an application module with identical state will be provided so it is logically the same application module instance each time. It is important to note that the framework makes the best effort it can to provide the same instance of application module for the same data control if it is available at the moment. This is done for better performance since the same application module does not need to activate the previous state which it still has intact after servicing the same data control during previous request. However, the data control is not guaranteed to receive the same instance for all its requests and if the application module that serviced that data control during previous is busy or unavailable, then a different application module will activate this data control's state. For this reason, it is not valid to cache references to application module objects, view objects, or view rows across HTTP requests in controller-layer code.
This mode was called the "Stateful Release Mode" in previous releases of JDeveloper.
Note:
If thejbo.ampool.doampooling
configuration property is false
— corresponding to your unchecking the Enable Application Module Pooling option in the Pooling and Scalability tab of the Business Components Configuration dialog — then there is effectively no pool. In this case, when the application module instance is released at the end of a request it is immediately removed. On subsequent requests made by the same user session, a new application module instance must be created to handle each user request, and pending state must be reactivated from the passivation store. Setting this property to false
is useful to discover problems in your application logic that might occur when reactivation does occur due to unpredictable load on your system. However, the property jbo.ampool.doampooling
set to false
is not a supported configuration for production applications and must be set to true
before you deploy your application. For further details, see Section 43.10, "Testing to Ensure Your Application Module is Activation-Safe."This mode implies that no state associated with this data control has to be preserved to survive beyond the current HTTP request. This level is the most efficient in performance because there is no overhead related to state management. However, you should limit its use to applications that require no state management, or to cases when state no longer needs to be preserved at this point. 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.
Performance Tip:
.The default release level is Managed, which implies that the application module's state is relevant and has to be preserved to allow the data control to span over several HTTP requests. Set release level to Unmanaged programmatically at runtime for particular pages to eliminate passivation and achieve better performance. A typical example is releasing the application module after servicing the HTTP request from a logout page.This mode was called the "Stateless Release Mode" in previous releases of JDeveloper.
This level guarantees that each data control will be assigned its own application module during its first request and for all subsequent requests coming from the HttpSession
associated with this data control. This data control will always receive the same physical instance of application module. This mode exists for legacy compatibility reasons and for very rare special use cases.
An example of using Reserved level occurs when there is a pending database state across a request resulting from the postChanges()
method or a PL/SQL stored procedure but not issuing a commit()
or rollback()
at the end of the request. In this case, if any other release level is used instead of Reserved, when the application module instance is recycled, a rollback is issued on the database connection associated with this application module instance and all uncommitted changes would be lost.
Performance Tip:
If you must use Reserved level, callsetReleaseLevel()
on the data control to keep its period as short as possible. For details about changing the release level programmatically, see Section 43.4, "Setting the Application Module Release Level at Runtime."Consequences of Reserved mode can be adverse. Reliability suffers because if for whatever reason the application module is lost, the data control will not be able to receive any other application module in its place from the pool, and so HttpSession
gets lost as well, which is not the case for managed level.
The failover option is ignored for an application module released with Reserved release level since its use implies your application absolutely requires working with the same application module instance on each request.
If your application employs subclassed entity objects, the key attribute of new entities must be prepopulated. If the key is not prepopulated, passivation and activation will fail. You can prepopulate the key attribute by overriding the create()
method or by using the DBSequence
type that will assign a temporary negative value to the key before the real value is fetched from the database after commit. For more information, see Section 4.10.10, "How to Get Trigger-Assigned Primary Key Values from a Database Sequence."
To enable Save For Later, you must first add Save Points to the application at points where you would like application state and data to be preserved if the end user leaves the application. You can use it to save data and state information about a region, view port, or portlet. Later, you use the Save Point Restore activity to restore application state and data associated with a Save Point.
For more information on how create and restore Save Points, see Section 22.6, "Using Save Points in Task Flows."
Save For Later can also perform implicit saves. These occur when data is saved automatically without the end user performing an explicit Save action when the user session times out or closes the browser window, for example.
For more information on how to perform an implicit save, see Section 22.6, "Using Save Points in Task Flows."
If you do not want to use the default "Managed State" release level for application modules, you can set your desired level programmatically.
To set a data control to release its application module using the unmanaged level, call the resetState()
method on the DCDataControl
class (in the oracle.adf.model.binding
package).
You can call this method any time during the request. This will cause application module not to passivate its state at all when it is released to the pool at the end of the request. Note that this method only affects the current application module instance in the current request. After this, the application module is released in unmanaged level to the pool, it becomes unreferenced and gets reset. The next time the application module is used by a client, it will be used in the managed level again by default.
Note:
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. This will happen automatically when theHTTPSession
times out, as described below.To set a data control to release its application module using the reserved level, call the setReleaseLevel()
method of the DCJboDataControl
class (in the oracle.adf.model.bc4j
package), and pass the integer constant ApplicationModule.RELEASE_LEVEL_RESERVED
.
When the release level for an application module has been changed to "Reserved" it will stay so for all subsequent requests until explicitly changed.
If you have set an application module to use reserved level, you can later set it back to use managed level by calling the setReleaseLevel()
method of the DCJboDataControl
class, and passing the integer constant ApplicationModule.RELEASE_LEVEL_MANAGED
.
Example 43-1 shows calling the resetState()
method on a data control named UserModuleDataControl
from the action method of a JSF backing bean.
Example 43-1 Calling resetState() on Data Control in a JSF Backing Bean Action Method
package devguide.advanced.releasestateless.controller.backing; import devguide.advanced.releasestateless.controller.JSFUtils; import oracle.adf.model.BindingContext; import oracle.adf.model.binding.DCDataControl; /** * JSF Backing bean for the "Example.jspx" page */ public class Example { /** * In an action method, call resetState() on the data control to cause * it to release to the pool with the "unmanaged" release level. * In other words, as a stateless application module. */ public String commandButton_action() { // Add event code here... getDataControl("UserModuleDataControl").resetState(); return null; } private DCDataControl getDataControl(String name) { BindingContext bc = (BindingContext)JSFUtils.resolveExpression("#{data}"); return bc.findDataControl(name); } }
Example 43-2 shows calling the resetState()
method on a data control named UserModuleDataControl
from the after-prepareRender phase of the ADF lifecycle using a custom ADF page phase-listener class. You would associate this custom class to a particular page by setting the ControllerClass
attribute on the page's page definition to the fully-qualified name of this class.
Example 43-2 Calling resetState() on Data Control in a Custom PagePhaseListener
package devguide.advanced.releasestateless.controller; import oracle.adf.controller.v2.lifecycle.Lifecycle; import oracle.adf.controller.v2.lifecycle.PagePhaseEvent; import oracle.adf.controller.v2.lifecycle.PagePhaseListener; import oracle.adf.model.binding.DCDataControl; public class ReleaseStatelessPagePhaseListener implements PagePhaseListener { /** * In the "after" phase of the final "prepareRender" ADF Lifecycle * phase, call resetState() on the data control to cause it to release * to the pool with the "unmanaged" release level. In other words, * as a stateless application module. * * @param event ADF page phase event */ public void afterPhase(PagePhaseEvent event) { if (event.getPhaseId() == Lifecycle.PREPARE_RENDER_ID) { getDataControl("UserModuleDataControl", event).resetState(); } } // Required to implement the PagePhaseListener interface public void beforePhase(PagePhaseEvent event) {} private DCDataControl getDataControl(String name, PagePhaseEvent event) { return event.getLifecycleContext() .getBindingContext() .findDataControl(name); } }
Example 43-3 shows calling the resetState()
method on a data control named UserModuleDataControl
from an overridden prepareRender()
method of a custom ADF page controller class. You would associate this custom class to a particular page by setting the ControllerClass
attribute on the page's page definition to the fully-qualified name of this class.
Note:
You can accomplish basically the same kinds of page-specific lifecycle customization tasks using a customPagePhaseListener
or a custom PageController
class. The key difference is that the PagePhaseListener
interface can be implemented on any class, while a custom PageController
must extend the PageController class in the oracle.adf.controller.v2.lifecycle
package.Example 43-3 Calling resetState() on Data Control in a Custom ADF PageController
package devguide.advanced.releasestateless.controller; import oracle.adf.controller.v2.context.LifecycleContext; import oracle.adf.controller.v2.lifecycle.PageController; import oracle.adf.controller.v2.lifecycle.PagePhaseEvent; import oracle.adf.model.binding.DCDataControl; public class ReleaseStatelessPageController extends PageController { /** * After calling the super in the final prepareRender() phase * of the ADF Lifecycle, call resetState() on the data control * to cause it to release to the pool with the "unmanaged" * release level. In other words, as a stateless application module. * * @param lcCtx ADF lifecycle context */ public void prepareRender(LifecycleContext lcCtx) { super.prepareRender(lcCtx); getDataControl("UserModuleDataControl", lcCtx).resetState(); } private DCDataControl getDataControl(String name, LifecycleContext lcCtx) { return lcCtx.getBindingContext().findDataControl(name); } }
If you wanted to build a Fusion web application where every request was handled in a completely stateless way, use a global custom PageLifecycle
class as shown in Example 43-4. For details on how to configure your application to use your custom lifecycle see Section 25.2, "About the JSF and ADF Page Lifecycles."
Example 43-4 Calling resetState() on Data Control in a Custom ADF PageLifecycle
package devguide.advanced.releasestateless.controller; import oracle.adf.controller.faces.lifecycle.FacesPageLifecycle; import oracle.adf.controller.v2.context.LifecycleContext; import oracle.adf.model.binding.DCDataControl; public class ReleaseStatelessPageLifecycle extends FacesPageLifecycle { /** * After calling the super in the final prepareRender() phase * of the ADF Lifecycle, call resetState() on the data control * to cause it to release to the pool with the "unmanaged" * release level. In other words, as a stateless application module. * * @param lcCtx ADF lifecycle context */ public void prepareRender(LifecycleContext lcCtx) { super.prepareRender(lcCtx); getDataControl("UserModuleDataControl", lcCtx).resetState(); } private DCDataControl getDataControl(String name, LifecycleContext lcCtx) { return lcCtx.getBindingContext().findDataControl(name); } }
The information saved by application model passivation is divided in two parts: transactional and non-transactional state. Transactional state is the set of updates made to entity object data – performed either directly on entity objects or on entities through view object rows – that are intended to be saved into the database. Non-transactional state comprises view object runtime settings, such as the current row index, WHERE
clause, and ORDER BY
clause.
The information saved as part of the application module passivation "snapshot" includes the following.
New, modified, and deleted entities in the entity caches of the root application module for this user session's (including old/new values for modified ones).
For each active view object (both statically and dynamically created):
Current row indicator for each row set (typically one)
New rows and their positions. (New rows are treated differently then updated ones. Their index in the view object is traced as well.)
ViewCriteria and all related parameters such as view criteria row, etc.
Flag indicating whether or not a row set has been executed
Range start and Range size
Access mode
Fetch mode and fetch size
Any view object-level custom data
Note:
Transient view object attributes can be saved if they are selected for passivation at design time. However, use this feature judiciously because this results in a snapshot that will grow in size with the number of rows that have been retrieved.SELECT
, FROM
, WHERE
, and ORDER BY
clause if created dynamically or changed from the View definition
Note:
If you enable ADF Business Components runtime diagnostics, the contents of each XML state snapshot are also saved. See Section 6.3.8, "How to Enable ADF Business Components Debug Diagnostics" for information on how to enable diagnostics.By default, passivation snapshots are saved in the database, but you can configure it to use the file system as an alternative.
The passivated XML snapshot is written to a BLOB
column in a table named PS_TXN
, using a connection specified by the jbo.server.internal_connection
property. Each time a passivation record is saved, it is assigned a unique passivation snapshot ID based on the sequence number taken from the PS_TXN_SEQ
sequence. The ADF session cookie held by the application module data control in the ADF binding context remembers the latest passivation snapshot ID that was created on its behalf and remembers the previous ID that was used.
The ADF runtime recognizes a configuration property named jbo.server.internal_connection
that controls which database connection and schema should be used for the creation of the PS_TXN
table and the PS_TXN_SEQ
sequence. If you don't set the value of this configuration parameter explicitly, then the state management facility creates the temporary tables using the credentials of the current application database connection.
To keep the temporary information separate, the state management facility uses a different connection instance from the database connection pool, but the database credentials are the same as the current user. Since the framework creates temporary tables, and possibly a sequence if they don't already exist, the implication of not setting a value for the jbo.server.internal_connection
is that the current database user must have CREATE TABLE
, CREATE INDEX
and CREATE SEQUENCE
privileges. Since this is often not desirable, Oracle recommends always supplying an appropriate value for the jbo.server.internal_connection
property, providing the credentials for a state management schema where table and schema be created. Valid values for the jbo.server.internal_connection
property in your configuration are:
A fully-qualified JDBC connection URL like:
jdbc:oracle:thin:
username
/
password
@
host
:
port
:
SID
A JDBC datasource name like:
java:/comp/env/jdbc/YourJavaEEDataSourceName
Performance Tip:
When creating thePS_TXN
table, use securefiles to store LOB data (the content column), and create a primary column index on the PS_TXN
table as global, partitioned reverse key index. The securefile configuration delivers superior performance over the basicfile configuration when working with LOB data. The reverse key index helps by reducing contention that can happen when the rate of inserts is high.Passivated information can be stored in several places. You can control it programmatically or by configuring an option in the application module configuration. The choices are database or a file stored on local file system:
File
This choice may be the fastest available, because access to the file is faster then access to the database. This choice is good if the entire middle tier (one or multiple Oracle Application Server installation(s) and all their server instances) is either installed on the same machine or has access to a commonly shared file system, so passivated information is accessible to all. Usually, this choice may be good for a small middle tier where one Oracle Application Server is used. In other words this is a very suitable choice for small middle tier such as one Oracle Application Server with all its components installed on one physical machine. The location and name of the persistent snapshot files are determined by jbo.tmpdir
property if specified. It follows usual rules of ADF property precedence for a configuration property. If nothing else is specified, then the location is determined by user.dir
if specified. This is a default property and the property is OS specific.
Database
This is the default choice. While it may be a little slower than passivating to file, it is by far the most reliable choice. With passivation to file, the common problem might be that it is not accessible to Oracle Application Server instances that are remotely installed. In this case, in a cluster environment, if one node goes down the other may not be able to access passivated information and then failover will not work. Another possible problem is that even if file is accessible to the remote node, the access time for the local and remote node may be very different and performance will be inconsistent. With database access, time should be about the same for all nodes.
To set the value of your choice in design time, set the property jbo.passivationstore
to database
or file
. The value null
will indicate that a connection-type-specific default should be used. This will use database passivation for Oracle or DB2, and file serialization for any others.
To set the storage programmatically use the method setStoreForPassiveState()
of interface oracle.jbo.ApplicationModule
. The parameter values that you can pass are:
PASSIVATE_TO_DATABASE
PASSIVATE_TO_FILE
Under normal circumstances, the ADF state management facility provides automatic cleanup of the passivation snapshot records.
When a passivation record is saved to the database on behalf of a session cookie, as described above, this passivation record gets a new, unique snapshot ID. The passivation record with the previous snapshot ID used by that same session cookie is deleted as part of the same transaction. In this way, assuming no server failures, there will only ever be a single passivation snapshot record per active end-user session.
The passivation snapshot record related to a session cookie is removed when the application module is checked into the pool with the unmanaged state level. This can occur when:
Your code specifically calls resetState()
on the application module data control.
Your code explicitly invalidates the HttpSession
, for example, as part of implementing an explicit "Logout" functionality.
The HttpSession
times out due to exceeding the session timeout threshold for idle time and failover mode is disabled (which is the default).
In each of these cases, the application module pool also resets the application module referenced by the session cookie to be "unreferenced" again. Since no changes were ever saved into the underlying database tables, once the pending session state snapshots are removed, there remains no trace of the unfinished work the user session had completed up to that point.
When the failover mode is enabled, if the HttpSession
times out due to session inactivity, then the passivation snapshot is retained so that the end user can resume work upon returning to the browser.
After a break in the action, when the end user returns to his browser and continues to use the application, it continues working as if nothing had changed. The session cookie is used to reactivate any available application module instance with the user's last pending state snapshot before handling the request. So, even though the users next request will be processed in the context of a new HttpSession
(perhaps even in a different application server instance), the user is unaware that this has occurred.
Note:
If an application module was released with reserved level then theHttpSession
times out, the user will have to go through authentication process, and all unsaved changes are lost.JDeveloper supplies the adfbc_purge_statesnapshots.sql
script to help with periodically cleaning up the application module state management table. You can find this file in the oracle_common
subdirectory of your Oracle Middleware installation directory (for example, ORACLE_HOME
\oracle_common\common\sql
).
Persistent snapshot records can accumulate over time if the server has been shutdown in an abnormal way, such as might occur during development or due to a server failure. Running the script in SQL*Plus will create the BC4J_CLEANUP
PL/SQL package. The two relevant procedures in this package are:
PROCEDURE Session_State(olderThan DATE)
This procedure cleans-up application module session state storage for sessions older than a given date.
PROCEDURE Session_State(olderThan_minutes INTEGER)
This procedures cleans-up application module session state storage for sessions older than a given number of minutes.
You can schedule periodic cleanup of your ADF temporary persistence storage by submitting an invocation of the appropriate procedure in this package as a database job.
You can use an anonymous PL/SQL block like the one shown in Example 43-5 to schedule the execution of bc4j_cleanup.session_state()
to run starting tomorrow at 2:00 am and each day thereafter to cleanup sessions whose state is over 1 day (1440 minutes) old.
Example 43-5 Scheduling Periodic Cleanup of the State Management Table
SET SERVEROUTPUT ON DECLARE jobId BINARY_INTEGER; firstRun DATE; BEGIN -- Start the job tomorrow at 2am firstRun := TO_DATE(TO_CHAR(SYSDATE+1,'DD-MON-YYYY')||' 02:00', 'DD-MON-YYYY HH24:MI'); -- Submit the job, indicating it should repeat once a day dbms_job.submit(job => jobId, -- Run the ADF Purge Script for Session State -- to cleanup sessions older than 1 day (1440 minutes) what => 'bc4j_cleanup.session_state(1440);', next_date => firstRun, -- When completed, automatically reschedule -- for 1 day later interval => 'SYSDATE + 1' ); dbms_output.put_line('Successfully submitted job. Job Id is '||jobId); END; . /
Since HTTP is a stateless protocol, the server receives no implicit notice that a client has closed his browser or gone away for the weekend. Therefore any Java EE-compliant server provides a standard, configurable session timeout mechanism to allow resources tied to the HTTP session to be freed when the user has stopped performing requests. You can also programmatically force a timeout.
You configure the session timeout threshold using the session-timeout tag in the web.xml
file. The default value is 35 minutes. When the HttpSession
times out the BindingContext
goes out of scope, and along with it, any data controls that might have referenced application modules released to the pool in the managed state level. The application module pool resets any of these referenced application modules and marks the instances unreferenced again.
To end a user's session before the session timeout expires, you can call the invalidate()
method on the HttpSession
object from a backing bean in response to the user's click on a Logout button or link. This cleans up the HttpSession
in the same way as if the session time had expired. Using JSF and ADF, after invalidating the session, you must perform a redirect to the next page you want to display, rather than just doing a forward. Example 43-6 shows sample code to perform this task from a Logout button.
Example 43-6 Programatically Terminating a Session
public String logoutButton_action() throws IOException{ ExternalContext ectx = FacesContext.getCurrentInstance().getExternalContext(); HttpServletResponse response = (HttpServletResponse)ectx.getResponse(); HttpSession session = (HttpSession)ectx.getSession(false); session.invalidate(); response.sendRedirect("Welcome.jspx"); return null; }
As with the implicit timeouts, when the HTTP session is cleaned up this way, it ends up causing any referenced application modules to be marked unreferenced.
It is fairly common practice to add custom user-defined information in the application module in the form of member variables or some custom information stored in oracle.jbo.Session
user data hashtable. The ADF state management facility provides a mechanism to save this custom information to the passivation snapshot as well, by overriding the passivateState()
method and either the activateState()
method or the prepareForActivation()
method in the ApplicationModuleImpl
class.
Note:
Similar methods are available on theViewObjectImpl
class and the EntityObjectImpl
class to save custom state for those objects to the passivation snapshot as well.You can override passivateState()
and activateState()
to ensure that custom application module state information is included in the passivation/activation cycle. Example 43-7 shows how this is done.
Note:
TheactivateState()
method is called at the end of the activation process after the view object have been activated. Most of the time this is where you want to place the application module state activation logic. However, if your application module activation logic needs to setup custom state information before the ADF statement management facility activates the view objects (for example, you might need to write custom code to allow the view objects to internally reference custom values at execution time), then the prepareForActivation()
method in the ApplicationModuleImpl
class would be the right place since it fires at the beginning of the activation process.In the example, jbo.counter
contains custom values you want to preserve across passivation and activation of the application module state. Each application module has an oracle.jbo.Session
object associated with it that stores application module-specific session-level state. The session contains a user data hashtable where you can store transient information. For the user-specific data to "survive" across application module passivation and reactivation, you need to write code to save and restore this custom value into the application module state passivation snapshot.
Example 43-7 Passivating and Activating Custom Information in the State Snapshot XML Document
/** * Overridden framework method to passivate custom XML elements * into the pending state snapshot document */ public void passivateState(Document doc, Element parent) { // 1. Retrieve the value of the value to save int counterValue = getCounterValue(); // 2. Create an XML element to contain the value Node node = doc.createElement(COUNTER); // 3. Create an XML text node to represent the value Node cNode = doc.createTextNode(Integer.toString(counterValue)); // 4. Append the text node as a child of the element node.appendChild(cNode); // 5. Append the element to the parent element passed in parent.appendChild(node); } /** * Overridden framework method to activate custom XML elements * into the pending state snapshot document */ public void activateState(Element elem) { super.activateState(elem); if (elem != null) { // 1. Search the element for any <jbo.counter> elements NodeList nl = elem.getElementsByTagName(COUNTER); if (nl != null) { // 2. If any found, loop over the nodes found for (int i=0, length = nl.getLength(); i < length; i++) { // 3. Get first child node of the <jbo.counter> element Node child = nl.item(i).getFirstChild(); if (child != null) { // 4. Set the counter value to the activated value setCounterValue(new Integer(child.getNodeValue()).intValue()+1); break; } } } } } /* * Helper Methods */ private int getCounterValue() { String counterValue = (String)getSession().getUserData().get(COUNTER); return counterValue == null ? 0 : Integer.parseInt(counterValue); } private void setCounterValue(int i) { getSession().getUserData().put(COUNTER,Integer.toString(i)); } private static final String COUNTER = "jbo.counter";
In Example 43-7, when activateState()
is overridden, the following steps are performed:
Search the element for any jbo.counter
elements.
If any are found, loop over the nodes found in the node list.
Get first child node of the jbo.counter
element.
It should be a DOM Text node whose value is the string you saved when your passivateState()
method above got called, representing the value of the jbo.counter
attribute.
Set the counter value to the activated value from the snapshot.
When passivateState()
is overridden, it performs the reverse job by doing the following:
Retrieve the value of the value to save.
Create an XML element to contain the value.
Create an XML text node to represent the value.
Append the text node as a child of the element.
Append the element to the parent element passed in.
Note:
The API's used to manipulate nodes in an XML document are provided by the Document Object Model (DOM) interfaces in theorg.w3c.dom
package. These are part of the Java API for XML Processing (JAXP). See the Javadoc for the Node
, Element
, Text
, Document
, and NodeList
interfaces in this package for more details.By default, all view objects are marked as passivation-enabled, so their state will be saved. However, view objects that have transient attributes do not have those attributes passivated by default. You can change how a view object is passivated, and even which attributes are passivated, using the Tuning page of the view object overview editor.
Each view object can be declaratively configured to be passivation-enabled or not. If a view object is not passivation enabled, then no information about it gets written in the application module passivation snapshot.
Performance Tip:
There is no need to passivate read-only view objects since they are not intended to be updated and are easily recreated from the XML definition. This eliminates the performance overhead associated with passivation and activation and reduces the CPU usage needed to maintain the application module pool.To set the passivation state of a view object:
In the Application Navigator, double-click a view object to open it in the overview editor.
On the General page, expand the Tuning section.
Select Passivate State to make sure the view object data is saved.
Optionally, you can select Including All Transient Attributes to passivate all transient attributes at this time, but see Section 43.8.4, "What You May Need to Know About Passivating Transient View Objects" for additional information.
The activation mechanism is designed to return your view object to the state it was in when the last passivation occurred. To ensure that, Oracle ADF stores in the state snapshot the values of any bind variables that were used for the last query execution. These bind variables are in addition to those that are set on the row set at the time of passivation. This approach works for an application that does not dynamically reset the WHERE clause and bind variables on each request. In this case, the set of bind variable values used for the last executeQuery()
and the set of bind variables current on the row set at passivation time are the same. The passivated state also stores the user-supplied WHERE clause on the view object related to the row set at the time of passivation.
However, when your application needs to dynamically change WHERE clauses and corresponding bind variables during the span of a single HTTP request, you need to ensure that the user-defined WHERE clause on the view object at the time of passivation matches the set of bind variable values used the last time the view object was executed before passivation. In this case, this is the correct sequence to follow:
(Request begins and AM is acquired.)
Call setWhereClause()
on a view object instance that references n bind variables.
Call setWhereClauseParam()
to set the n values for those n bind variables.
Call executeQuery()
.
Call setWhereClause(null)
to clear WHERE clause.
Call setWhereClauseParams(null)
to clear the WHERE clause bind variables.
(AM is released.)
If you do not adhere to this strategy of changing runtime view object settings before using them, your application will fail with an SQL exception during application module pooling state activation:
JBO-27122: SQLStmtException: <... SQL Statement ...>
Because many of the view object's instance settings are saved in the passivation state snapshot and restored on activation (as described in Section 43.5.1, "State Information Saved During Passivation"), it is not advisable to change any of these settings just after executing the view object if you won't be re-executing the view object again during the same block of code (and so, during the same HTTP request). Instead, change the view object instance settings the next time you need them to be different before executing the query.
If you are dynamically adding named WHERE
clause parameters to your view object instances, you might find it useful to add the following helper method to your ViewObjectImpl
framework extension class. This method removes named bind variables that have been added to the view instance at runtime, without removing the ones that have been declaratively defined on the view definition at design time.
protected void clearWhereState() { ViewDefImpl viewDef = getViewDef(); Variable[] viewInstanceVars = null; VariableManager viewInstanceVarMgr = ensureVariableManager(); if (viewInstanceVarMgr != null) { viewInstanceVars = viewInstanceVarMgr.getVariablesOfKind(Variable.VAR_KIND_WHERE_CLAUSE_PARAM); if (viewInstanceVars != null) { for (Variable v: viewInstanceVars) { // only remove the variable if its not on the view def. if (!hasViewDefVariableNamed(v.getName())) { removeNamedWhereClauseParam(v.getName()); } } } } getDefaultRowSet().setExecuteParameters(null, null, true); setWhereClause(null); getDefaultRowSet().setWhereClauseParams(null); } private boolean hasViewDefVariableNamed(String name) { boolean ret = false; VariableManager viewDefVarMgr = getViewDef().ensureVariableManager(); if (viewDefVarMgr != null) { try { ret = viewDefVarMgr.findVariable(name) != null; } catch (NoDefException ex) { // ignore } } return ret; }
Because view objects are marked as passivated by default, a transient view object — one that contains only transient attributes — is marked to be passivation enabled, but only passivates its information related to the current row and other non-transactional state.
Performance Tip:
Transient view object attributes are not passivated by default. Due to their nature, they are usually intended to be read-only and are easily recreated. So, it often doesn't make sense to passivate their values as part of the XML snapshot. This also avoids the performance overhead associated with passivation and activation and reduces the CPU usage needed to maintain the application module pool.To individually set the passivation state for transient view object attributes:
In the Application Navigator, double-click a view object to open it in the overview editor.
On the Attributes page, select the transient attribute you want to passivate and then click the Details tab.
In the Details section, select the Passivate checkbox.
Passivating transient view object attributes is more costly resource-wise and performance- wise, because transactional functionality is usually managed on the entity object level. Since transient view objects are not based on an entity object, this means that all updates are managed in the view object row cache and not in the entity cache. Therefore, passivating transient view objects or transient view object attributes requires special runtime handling.
Usually passivation only saves the values that have been changed, but with transient view objects passivation has to save entire row. The row will include only the view object attributes marked for passivation.
Using passivation, you can use a view object to store one or more global variables, each on a different transient attribute. When you mark a transient attribute as passivated, the ADF Business Components framework will remember the transient values across passivation and activation in high-throughput and failover scenarios. Therefore, it is an easy way to implement a session-level global value that is backed up by the state management mechanism, instead of the less-efficient HTTP Session replication. This also makes it easy to bind to controls in the UI if necessary.
There are two basic approaches to store values between invocations of different screens, one is controller-centric, and the other is model-centric.
Implementation of the task in the ADF controller
The controller-centric approach involves storing and referencing values using attributes in the pageFlow scope. This approach might be appropriate if the global values do not need to be referenced internally by any implementations of ADF Business Components.
For more information about pageFlow scope, see Section 18.2.4, "What You May Need to Know About Memory Scope for Task Flows."
Implementation of the task in the ADF model
The model-centric approach involves creating a transient view object, which is conceptually equivalent to a non-database block in Oracle Forms.
Create a new view object using the View Object Wizard, as described in Section 5.2.1, "How to Create an Entity-Based View Object."
On step 1 of the wizard, select the option for Rows populated programmatically, not based on a query.
On step 2, click New to define the transient attribute names and types the view object should contain. Make sure to set the Updateable option to Always.
Click Finish and the newly-created view object appears in the overview editor.
Disable any queries from being performed in the view object.
On the General page of the overview editor, expand the Tuning section, and in the Retrieve from Database group box, select the No Rows option.
Make sure data in the view object is not cleared out during a rollback operation. To implement this, you enable a custom Java class for the view object and override two rollback methods.
On the Java page of the overview editor, click the Edit icon in the Java Classes section to open the Java dialog.
In the Java dialog, select Generate View Object Class and click OK.
In the overview editor, click on the hyperlink next to the View Object Class in the Java Classes section to open the source editor.
From the Source menu, choose Override Methods.
In the Override Methods dialog, select the beforeRollback()
and afterRollback()
methods to override, and click then OK.
In both the beforeRollback()
and afterRollback()
methods, comment out the call to super
in the Java code.
Add an instance of the transient view object to your application module's data model, as described in Section 9.2.3.2, "Adding Master-Detail View Object Instances to an Application Module."
Create an empty row in the view object when a new user begins using the application module.
Enable a Java class for your application module if you don't have one yet.
Override the prepareSession()
method of the application module, as described in Section 9.11.1, "How to Override a Built-in Framework Method."
After the call to super.prepareSession()
, add code to create a new row in the transient view object and insert it into the view object.
Now you can bind read-only and updateable UI elements to the "global" view object attributes just as with any other view object using the Data Controls panel.
In the database server you are likely familiar with the savepoint feature that allows a developer to rollback to a certain point within a transaction instead of rolling back the entire transaction. An application module offers the same feature but implemented in the middle tier.
Best Practice:
Oracle ADF provides a declarative approach to working with savepoints, described in Section 22.6, "Using Save Points in Task Flows." Use the programmatic approach described in Section 43.9.1, "How to Use State Management for Savepoints" only if the declarative approach doesn't meet your needs.To use state management for implementing middle-tier savepoints, you override three methods in the oracle.jbo.ApplicationModule
interface
public String passivateStateForUndo(String id,byte[] clientData,int flags) public byte[] activateStateForUndo(String id,int flags) public boolean isValidIdForUndo(String id)
You can use these methods to create a stack of named snapshots and restore the pending transaction state from them by name. Keep in mind that those snapshots do not survive past duration of transaction (for example, events of commit or rollback). This feature could be used to develop complex capabilities of the application, such as the ability to undo and redo changes. Another ambitious goal that could exploit this functionality would be functionality to make the browser back and forward buttons behave in an application-specific way. Otherwise, simple uses of these methods can come quite in handy.
If you have not explicitly tested that your application module functions when its pending state gets activated from a passivation snapshot, then you may encounter an unpleasant surprise in your production environment when heavy system load tests this aspect of your system for the first time.
The jbo.ampool.doampooling
configuration property corresponds to the Enable Application Module Pooling option in the Pooling and Scalability tab of the Business Components Configuration dialog. By default, this checkbox is checked so that application module pooling is enabled. Whenever you deploy your application in a production environment the default setting of jbo.ampool.doampooling
to true
is the way you will run your application. But, as long as you run your application in a test environment, setting the property to false
can play an important role in your testing. When this property is false, there is effectively no application pool. When the application module instance is released at the end of a request it is immediately removed. On subsequent requests made by the same user session, a new application module instance must be created to handle it and the pending state of the application module must be reactivated from the passivation store.
As part of your overall testing plan, you should adopt the practice of testing your application modules with the jbo.ampool.doampooling
configuration parameter set to false
. This setting completely disables application module pooling and forces the system to activate your application module's pending state from a passivation snapshot on each page request. It is an excellent way to detect problems that might occur in your production environment due to assumptions made in your custom application code.
Caution:
It is important to reenable application module pooling after you conclude testing and are ready to deploy the application to a production environment. The configuration propertyjbo.ampool.doampooling
set to false
is not a supported configuration for production applications and must be set to true
before deploying the application.For example, if you have transient view object attributes you believe should be getting passivated, this technique allows you to test that they are working as you expect. In addition, consider situations where you might have introduced:
Private member fields in application modules, view objects, or entity objects
Custom user session state in the Session
user data hashtable
Your custom code likely assumes that this custom state will be maintained across HTTP requests. As long as you test with a single user on the JDeveloper Integrated WebLogic Server, or test with a small number of users, things will appear to work fine. This is due to the "stateless with affinity" optimization of the ADF application module pool. If system load allows, the pool will continue to return the same application module instance to a user on subsequent requests. However, under heavier load, during real-world use, it may not be able to achieve this optimization and will need to resort to grabbing any available application module instance and reactivating its pending state from a passivation snapshot. If you have not correctly overridden passivateState()
and activateState()
(as described in Section 43.7, "Managing Custom User-Specific Information") to save and reload your custom component state to the passivation snapshot, then your custom state will be missing (i.e. null
or back to your default values) after this reactivation step. Testing with jbo.ampool.doampooling
set to false allows you to quickly isolate these kinds of situations in your code.
The ADF state management mechanism relies on passivation and activation to manage the state of an application module instance. Implementing this feature in a robust way is only possible if all pending changes are managed by the application module transaction in the middle tier. The most scalable strategy is to keep pending changes in middle-tier objects and not perform operations that cause pending database state to exist across HTTP requests. This allows the highest leverage of the performance optimizations offered by the application module pool and the most robust runtime behavior for your application.
When the jbo.doconnectionpooling
configuration parameter is set to true
— typically in order to share a common pool of database connections across multiple application module pools — upon releasing your application module to the application module pool, its JDBC connection is released back to the database connection pool and a ROLLBACK
will be issued on that connection. This implies that all changes which were posted but not commited will be lost. On the next request, when the application module is used, it will receive a JDBC connection from the pool, which may be a different JDBC connection instance from the one it used previously. Those changes that were posted to the database but not commited during the previous request are lost.
Caution:
When thejbo.doconnectionpooling
configuration parameter is set to true
— typically in order to share a common pool of database connections across multiple application module pools — upon releasing your application module to the application module pool, its JDBC connection is released back to the database connection pool and a ROLLBACK
will be issued on that connection. This implies that all changes which were posted but not commited will be lost. On the next request, when the application module is used, it will receive a JDBC connection from the pool, which may be a different JDBC connection instance from the one it used previously. Those changes that were posted to the database but not commited during the previous request are lost.
The jbo.doconnectionpooling
configuration parameter is set by checking the Disconnect Application Module Upon Release property on the Pooling and Scalability tab of the Business Components Configuration dialog.
Oracle recommends using optimistic locking, the default mode for web applications. Pessimistic locking should not be used for web applications as it creates pending transactional state in the database in the form of row-level locks. If pessimistic locking is set, state management will work, but the locking mode will not perform as expected. Behind the scenes, every time an application module is recycled, a rollback is issued in the JDBC connection. This releases all the locks that pessimistic locking had created.
Performance Tip:
Always use the default mode optimistic locking for web applications. Only optimistic locking is compatible with the application module unmanaged release level mode, which allows the application module instance to be immediately released when a web page terminates. This provides the best level of performance for web applications that expect many users to access the application simultaneously.To ensure your configuration uses optimistic locking, open the Properties tab of the Business Components Configuration dialog and confirm that the value of the jbo.locking.mode
property is set to optimistic
or optupdate
.
Note:
To open the Business Components Configuration dialog, right-click the application module in the Application Navigator, and choose Configurations from the context menu. Then, in the Manage Configurations dialog, select the configuration you want to edit and click Edit.Optimistic locking (optimistic
) issues a SELECT FOR UPDATE
statement to lock the row, then detects whether the row has been changed by another user by comparing the change indicator attribute — or, if no change indicator is specified, the values of all the persistent attributes of the current entity as they existed when the entity object was fetched into the cache.
Optimistic update locking (optupdate
) does not perform any locking. The UPDATE
statement determines whether the row was updated by another user by including a WHERE
clause that will match the existing row to update only if the attribute values are unchanged since the current entity object was fetched.
The transaction-level postChanges()
method exists to force the transaction to post unvalidated changes without committing them. This method is not recommended for use in web applications unless you can guarantee that the transaction will definitely be committed or rolled-back during the same HTTP request. Failure to heed this advice can lead to strange results in an environment where both application modules and database connections can be pooled and shared serially by multiple different clients.
If for some reason you need to create a transactional state in the database in some request by invoking postChanges()
method or by calling PL/SQL stored procedure, but you cannot issue a commit or rollback by the end of that same request, then you must release the application module instance with the reserved level from that request until a subsequent request when you either commit or rollback.
Performance Tip:
Use as short a period of time as possible between creation of transactional state in the database and performing the concluding commit or rollback. This ensures that reserved level doesn't have to be used for a long time, as it has adverse effects on application's scalability and reliability.Once an application module has been released with reserved level, it remains at that release level for all subsequent requests until release level is explicitly changed back to managed or unmanaged level. So, it is your responsibility to set release level back to managed level once commit or rollback has been issued.
For more information, see Section 43.4, "Setting the Application Module Release Level at Runtime."