Oracle9i Application Server Best Practices Release 2 (9.0.3) Part Number B10578-02 |
|
This chapter describes Oracle9iAS framework best practices. The topics include:
Freeware such as Struts and Webwork can run on top of Oracle9iAS. For features that leverage more of Oracle9iAS with closer integration, Oracle9iAS and JDeveloper include frameworks such as an MVC framework, UIX, and BC4J.
The selection of an architecture solution is a crucial decision with major impact. A de-facto standard architecture that fulfills the above stated objectives is the Model-View-Controller (MVC) architecture.
The central idea of the MVC architecture is to separate business logic, presentation, and program flow. By using the MVC architecture in a J2EE application, core data access functionality is separated from the presentation and control logic that uses this functionality. Such separation allows multiple views to share the same enterprise data model, which makes supporting multiple clients easier to implement, test, and maintain. The key concepts of MVC architecture are:
This section describes BC4J best practices and includes:
The business components framework is organized around interfaces in the oracle.jbo
package, such as oracle.jbo
.ApplicationModule
, oracle.jbo.ViewObject
, and oracle.jbo.Row
. By coding your client to these interfaces, rather than the classes that implement them, your code can stay tier-independent. If, for example, you choose to deploy your business components as an EJB, you can use the same client code you used for testing when your business components were deployed locally, because the interfaces are always accessible in the client tier. Your code doesn't need to use a wire protocol; the business components framework handles all issues of cross-tier communication for you.
The interfaces in oracle.jbo
are implemented by classes in the package oracle.jbo.server
. For example, oracle.jbo.ApplicationModule
is implemented by oracle.jbo.server.ApplicationModuleImpl
. You should not directly call methods on these implementation classes, because if you deploy remotely, these classes will not be available on the client tier. Invoking the classes would prevent you from ever deploying your application remotely. Instead, you should call methods on the interfaces.
Similarly, when you create your own application modules and view links, you should not call methods on their implementation classes. Instead, you should export methods to create custom interfaces that contain them. These interfaces extend oracle.jbo.ApplicationModule
or oracle.jbo.ViewObject
and are also always accessible in the client tier. When you downcast a business component, always downcast to your interface and never to your implementation class.
Your application will have the best performance and scalability if you deploy your business components to the Web module with your client. Unless you have strong reasons (such as wanting to use distributed transactions or EJB security features), we recommend Web module deployment of business components over EJB deployment.
Note that both Web module deployment and EJB deployment are fully J2EE-compliant, and the BC4J framework makes it easy to switch between them. You can test your application in both modes to see which gives you the best performance.
A client can use application module instances from a pool, called application module pooling. Application module pooling has these advantages:
For example, in the case of a Web application, you may have 1,000 users but you know that only 100 will be using a certain application module at one time. Hence, you can use an application module pool. When a client needs an application module instance, it takes a free one from the pool and releases it to the pool after either committing or rolling back the transaction. Because the instance is pre-created, end users are saved the time it takes to instantiate the application module when they want to perform a task. Typically, Web-based JSP clients use pools. If you want to make sure that the application module pool has a maximum of 100 application module instances, you can customize the default application module pool.
If your client needs to keep track of application module state, we recommend using stateful mode. In a stateful JSP application, the client does not reserve the application module instance, making it available to other clients if the number of application modules exceeds the recycle threshold. State is, instead, preserved in one of two ways: the application module pool returns a client's original application module if the application module has not been recycled, and the pool persists the state of recycled application modules in the database to be available to clients that request them later.
When you release an application module at the end of a user's session, be sure to use stateless (rather than stateful or reserved) release mode. This frees up database space and allows the pool to recycle the application module immediately.
Opening a connection to a database is a time-consuming process that can sometimes take longer than getting the data itself. The advantage of connection pooling is that clients that need to instantiate a new application module can have faster response times because they are saved the time of creating the database connection. With database connection pooling, they can reuse a connection that another application module instance already created.
Particularly in large organizations, you may want specific functionality shared by all components of a particular type--for example, by all view objects. An architect can create a thin layer of classes such as MyOrgViewObjectImpl
that implement the desired behavior. Individual developers can extend MyOrgViewObjectImpl
instead of ViewObjectImpl
, and you can use the "substitutes" feature to extend MyOrgViewObjectImpl
in legacy code.
Basing a view object on an entity object allows you to use the view object to insert, update, and delete data, and helps keep view objects based on the same data synchronized. However, if your view object is only going to be used for read-only queries, and there is no chance that the data being queried in this view object will have pending changes made through another view object in the same application module, you should use a SQL-only view object that has no underlying entities. This will give you improved performance since rows do not need to be added to an entity cache.
If you are scrolling through data in one direction, such as formatting data for a Web page, or for batch operations that proceed linearly, you can use a forward-only view object. Forward-only mode prevents data from entering the view cache. Using forward-only mode can save memory resources and time, because only one view row is in memory at a time. Note that if the view object is based on one or more entity objects, the data does pass to the entity cache in the normal manner, but no rows are added to the view cache.
A root application module should correspond to one task--anything that you would include in a single database transaction. Do not put more view objects or view links than you will need for a particular task in a single application module.
In addition, consider deferring the creation of view links by creating them dynamically with createViewLink()
. If you include all view links at design time, the business logic tier will automatically execute queries for all detail view objects when your client navigates through a master view object. Deferring view link creation will prevent the business logic tier from executing queries for detail view objects that you do not yet need.
For example, for a form in which detail rows are displayed only on request (rather than automatically), including a view link at design time would force the business logic tier to automatically execute a query that might well be unnecessary. To prevent this, you should create a view link dynamically when the detail rows are requested. By contrast, for a form in which detail rows are displayed as soon as a master is selected, you should use a view link created at design time to avoid the runtime overhead of calling createViewLink()
.
By default, the application module pool supports failover, which saves an application module's state to the database as soon as the application module is checked into the pool. If the business logic tier or the database becomes inoperable in mid-transaction (due to a power failure or system crash, for example), the client will be able to instantiate a new application module with the same state as the lost one, and no work will be lost.
However, some applications do not require this high level of reliability. If you're not worried about loss of work due to server problems, you may want to disable failover. When failover is disabled, the application module's state exists only in memory until it is committed to the database (at which point the application module's state is discarded) or recycled (at which point the state is saved so that the client can retrieve it). By not saving the application module state every time the application module is checked in, failover-disabled mode can improve performance.
While the business logic tier is running, it stores view rows in a cache in memory (the Java heap). When the business logic tier needs to store many rows at once, you need to make sure it doesn't run out of memory. To do so, you can specify that when the number of rows reaches a certain size, the rows "overflow" to your database to be stored on disk. This feature is called view row spillover. If your application needs to work with a large query result, view row spillover can help the cache operate more efficiently.
You should include any portion of your query condition that you know in advance in the WHERE
clause field in the View Object wizard. Only use setWhereClause()
for genuinely dynamic query conditions. Even if your query conditions are genuinely dynamic, you may be able to use parameterized queries instead of the setWhereClause()
. For example, if your view object needs to execute a query with the WHERE
clause "EMPLOYEE_ID=<x>
" for various values of x
, use a parameterized WHERE
clause such as "EMPLOYEE_ID=:1
". This is more efficient than repeatedly calling setWhereClause()
.
The default JDBC fetch size is optimized to provide the best trade-off between memory usage and network usage for many applications. However, if network performance is a more serious concern than memory, consider raising the JDBC fetch size.
In non-interactive, batch processes, there is no reason for view objects to listen for entity object events. Use ViewObject.setListenToEntityEvents(false)
on such view objects to eliminate the performance overhead of event listening.
Oracle-style bind parameters (:1,:2, and so on) are perform better than JDBC-style bind parameters.
There are only two reasons to use JDBC-style bind parameters:
WHERE
clause.
This section describes Java object cache best practices and includes the following topics:
If a CacheAccess.waitForResponse
or CacheAccess.releaseOwnership
method call times out, it must be called again until it returns successfully. Or, one of the following should be eventually called to free up resources:
When an object or group is defined as SYNCHRONIZE
, ownership is required to load or replace the object, but it is not required for general access to the object or to invalidate the object.
On Solaris by default, a process is allowed only 64 open file descriptors. This is insufficient for most disk cache use. 1024 or greater is a more appropriate value.
In general, objects stored in the cache should be defined using the system class loader (defined in CLASSPATH
when the JVM is initialized) rather than in user defined class loaders. Specifically, any objects that will be shared between applications or may be saved or spooled to disk need to be defined in the system CLASSPATH
. Failure to do so may result in ClassNotFoundExceptions
or ClassCastExceptions
.
When destroy
or invalidate
is called on a group, if there are conflicts between local and distributed definitions of the group and objects in the group, distributed wins. That is, if the group is distributed, all objects in the group will be invalidated/destroyed across the entire cache system regardless if the individual objects or associated groups are defined as local. If the group is defined as local, then local objects within the group will only be invalidated locally, distributed objects will be invalidated throughout the entire cache system.
Local objects (objects not defined with the Attributes.DISTRIBUTE
flag) saved to disk using the CacheAccess.save
method will not survive the termination of the process. By definition, local cache objects are only visible to the cache instance they were loaded into. If that cache instance goes away for any reason, the objects it manages, including those on disk, are lost. If an object needs to survive process termination, both the object and the cache need to be defined as DISTRIBUTE
.
The cache environment, region, group, and object definitions, are local to a cache. They are not saved to disk nor propagated to other caches. This environment should be defined during the initialization of the application.
A CacheAccess
object should always be closed when it is no longer used. The CacheAccess
objects are pooled. and acquire other cache resources on behalf of users. If access objects are not closed when they are no longer used, these resources will not be returned to the pool and will not be cleaned up until they are garbage collected by the JVM. If CacheAccess
objects are continually allocated and not closed, they can cause a significant loss in available resources and a consequent degradation in performance.
A CacheAccess
object only holds a reference to one cached object at a time. If multiple cached objects are being accessed concurrently, multiple CacheAccess
objects should be used. For objects stored in memory, the consequences of not doing this are minor since Java will prevent the cached object from being garbage collected even if the cache believes it is not being referenced.
For objects stored on disks, if the cache reference is not maintained, the underlying file could be removed by another user or by time-based invalidation, causing unexpected exceptions. It is best to always keep the cache reference open as long as the cached object is being used. This allows the cache system to better manage its resources.
The CacheAccess
object should not be shared between threads. This object represents a "user" to the caching system. It contains the current state of that user's access to the cache, which object is currently being accessed, which objects are currently "owned", etc. Trying to share the CacheAccess
object is unnecessary and can result in non-deterministic behavior.
|
![]() Copyright © 2003 Oracle Corporation. All Rights Reserved. |
|