How Does the Business Logic Tier Cache Data?

The business logic tier stores data it receives from a database and clients in two types of caches: an entity cache and a view cache. This sophisticated, patented caching system handles many complex design patterns and programmatic tasks for you, so you can concentrate on implementing the business logic and client interfaces specific to your application.

You could implement an entire business components application without ever learning how the caching system works. However, it's useful to understand its major features so you can make informed implementation and optimization decisions. In addition, experienced Java programmers can customize or bypass the caching system for application-specific reasons (although it's rarely necessary). After reading this topic, you should have an understanding of what the caching system does for you and what aspects of the caching system you can control.

Why does the business logic tier use caches?

The business logic tier caches business component instances in memory for five main reasons:

What classes make up the caches

Several framework classes in the oracle.jbo.server package affect the caches. It's useful to have a general idea of this relationship to understand how your code uses the caches.

For each EntityDefImpl instance used in a session, the top-level application module manages an entity cache that contains EntityImpl instances.

A view object manages a view cache. The view cache contains row sets. The row sets contain ViewRowImpl instances.

To better understand these relationships, consider a simple example where an application begins running, retrieves data from the database, and later updates the data in the database.

How a query first populates empty caches

Typically, when an application first starts running, a view object query populates the caches. When the view object query executes, each row of the query's result set is cached in a view row:

The process of populating the caches follows these general steps:

  1. The client creates a top-level application module.

    To use the business logic tier, a client creates a top-level application module instance (also called a root application module), which can contain view object instances, view link instances, and nested application module instances. Each top-level application module instance has its own caches and connection to the database (session). The instance also provides a transaction context for the view object instances it contains, including those in any nested application module instances. So the top-level application module instance is a container for view object instances and nested application module instances that share caches, a connection, and transactions.

  2. The client finds or creates a view object.

    If the view object was created by the application module, the client would "find" it. Otherwise, it would create it.

  3. The view object executes its query.

    Each view object typically contains a SQL query. The client calls a method to execute the query, or the client asks for one or more rows (which causes the view object to execute its query if it hasn't already).

  4. The view object retrieves the initial rows of requested data from the database.

    Alternatively, the client could request all rows.

    By default, the view object does not fetch all rows in the result set at once, unless specified. For example, the first method will bring in the first row and leave the rest out of the cache. The next method brings in the next row, and so on.

  5. The view object creates a blank view row for each retrieved database row. The view object creates an empty EntityImpl instance for each entity object related to this view object.

  6. The view object adds attribute values to the instances.

    The view object populates the EntityImpl instances with mapped attributes. The view object populates the view row with attributes that are not persisted in the database.

  7. The view row adds EntityImpl instances to the entity cache.

    The instances are identified by their primary key.

  8. The view row is added to the row set in the view cache.

For example, in the following code snippet, the client creates an application module instance, finds a view object, adjusts the view object query, retrieves the first row, and gets the salary of the first row. All view attributes based on an entity attribute are added to the entity cache. All view attributes that are not based on an entity attribute are added to the view cache. For the Managers view object, all attributes are persistent, so the view object places all attribute values in the entity cache.

An application does not have to use a view cache or entity cache.

If there are no view attributes that are based on entity attributes, the entity cache is not used.

To prevent data from entering the view cache, you can use the setForwardOnly method in a view object. This is called forward only mode. Using forward only mode can save memory resources and time, because only one view row is in memory at a time. Forward only mode is useful when you are fetching data and reading it once, such as formatting data for a web page, or for batch operations that proceed linearly. 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.

In addition, an entity object can populate the entity cache directly, bypassing the view cache. If you look up an entire entity object with the findByPrimaryKey method, all of its attribute values are added to the entity cache. (It gives priority to data in the cache, and then brings in data from the database if it's not already there, as described later.) Using a view object to look up data is more flexible, in that you can bring in any attributes that are relevant to the task and leave out those that are not. The view object executes a query, and if the attribute doesn't already contain data, it adds the data for that attribute.

How you create a new row

Inserting a new row involves these general steps:
  1. A client calls the createRow method.

    The view object creates a blank view row. The view row and EntityImpl are not in the cache yet.

  2. The client sets the primary key.

    The new EntityImpl is added to entity cache. The new row enters the transaction list when it goes into the entity cache.

    If the ROWID is the primary key, the framework assigns a temporary value.

  3. The client calls the insertRow method on the view object.

    A new view row is added to view cache.

You can auto-create a primary key (as a sequence, GUID, and so on) whose value is generated by the database when a new row is posted to it. If you want to use to use an entity attribute as a database sequence, for example, you could add code to the EntityImpl create method, select the Refresh After Insert attribute setting, or use the DBSequence domain. The code you in the create method would need to return the value immediately when a new row is inserted. For the Refresh After Insert setting, the value is returned after the row is saved to the database; you need a database trigger that inserts the value into the column of a new row. You may want to make these types of attributes read-only so that an end user cannot change it.

If a query returns multiple row sets because of a bind variable, multiple row sets can be created. For example, if the query is SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE LOC=:1, and the bind values are SAN JOSE, CHICAGO, and NEW YORK, there can be three row sets, one for each location, in the view cache.

How data merge occurs in the caches

Each view object instance has its own view cache. After a query, each returned database row is represented by a row in the view cache. Each column is represented by a corresponding attribute in the row. Attributes that did not receive a value remain empty in the row.

Each entity object instance has its own entity cache. After a view object query, in each returned row, the values of view attributes that map to entity attributes are placed in the appropriate entity cache. Remember that view objects can map to more than one entity object. So different attributes in a row can end up in more than one entity cache. Each row in an entity cache is uniquely identified by its primary key. (So if you specify more than one entity object for a view object, you must always include the primary key of each entity object.) Attributes whose values were not populated by the query remain empty in the row. Whenever a view attribute that is mapped to an entity object is retrieved or set, it delegates to the getter or setter in the entity object.

Each view object instance contained by a top-level application module shares entity caches with all other view object instances in the top-level application module. (This includes any view object instances in nested application modules.) If you have several view objects that refer to the same entity object, you know that they also refer to the same entity cache. This means that when shared data is modified through one view object instance, all other view object instances are notified of this change and can show the modification in a client form, for example.

When a view object executes a query, the data needs to be merged into the entity cache. The business logic tier uses modified data in the entity cache, if it exists. If an entity object instance has been modified, the query returns the entity cache's data instead of new data from the database. If the entity object instance does not exist or is unmodified, the query returns data from the database. This ensures that unposted changes are not overwritten when the same row is retrieved from database through other view object instances. The entity object checks its cached data against database data at locking time.

Here's an example:

The following code snippet shows how different view objects can view the same cached data.

When fault-in occurs

In the entity cache, attributes may be missing, for example, if a view object does not have a view attribute that maps to a particular entity attribute so that attribute is never populated. If business logic tries to access an attribute value stored in a database row, and the value hasn't been brought into the entity cache, fault-in occurs. All empty attributes in the row are populated from the database so that business logic can complete. Here's an example:

  1. An Employees view object has Employee_Id and Last_Name attributes.
  2. An Employees entity object has Employee_Id, Last_Name, and Salary attributes.
  3. The view object query brings Employee_Id and Last_Name attributes into the entity cache, but the Salary attribute is unpopulated.
  4. The validation method accesses Salary.
  5. The business logic tier gets all row attributes from the database.

Fault-in ensures that business logic has values it needs. As an optimization technique, you can prevent fault-in by making sure that all attributes needed by business logic are included in view object queries. This prevents extra SQL statements from being executed and creates a clean separation between entity objects and view objects. However, you may want to keep rarely accessed attributes out of the view object to conserve memory.

The following entity object code snippet, from EmployeeImpl.java, shows why fault-in will occur. The CommissionPct attribute is accessed, but not in the cache, so it's faulted-in.

The following code snippet, in TestClient.java, shows where fault-in occurs.

When all attributes are brought into the entity cache

There are four conditions under which all attributes of a row in the entity cache are brought in:

How locking occurs

There are two types of locking: pessimistic and optimistic. Or you can choose not to use locking at all, which is typically not the best choice. You usually use optimistic locking when you are optimistic that two users will never modify the same row at the same time, due to the nature of your application. Pessimistic locking is the most conservative form of locking.

When an application uses locking, the business logic tier attempts to lock a row in these circumstances:

When an application doesn't use locking, if the row is locked by another user, the entity object will wait until the row is released by the other user who locked it. Then it will write the changes, which could overwrite the previous user's changes.

Here is how the business logic tier acquires a lock:

  1. The business logic tier sends a DML statement to the database to lock the row.
    It uses this general syntax:
    select ... from ... where PK = value for update nowait
  2. If the lock succeeds, it retrieves the latest data from the database (with same statement).
  3. The business logic tier resolves the database data with the data in the cache, as described in the next section.

When the transaction associated with a top-level application module uses pessimistic locking (the default), and the row is an existing row in the database, the business logic tier attempts to lock a database row the first time one of its attributes is successfully modified, including passing any validation. (If the entity object is part of a composition, the framework first tries to lock the row for the top-level entity object in the composition.) If it can lock the row, the business logic tier can modify the row. If the business logic tier cannot lock the row, it throws an exception and the value returns to what it was before the modification.

When the transaction of a top-level application module uses optimistic locking, at post time, the business logic tier attempts to lock each row corresponding to any deleted or modified entity object instances. (If the entity object is part of a composition, the framework first tries to lock the row associated with the top-level entity object.) If any of the attempts to lock are unsuccessful, an exception is thrown.

For session-oriented programs, pessimistic locking is best, because the application discovers errors immediately and there is less chance of the transaction aborting when it tries to commit. For non–session-oriented programs, optimistic locking can be best, for example, you don't want to lock a record if a user leaves a web page open on their screen for a few days.

Locking is defined in the oracle.jbo.Transaction interface. You set the locking mode with the setLockingMode method, and
get the current locking mode with the getLockingMode method. After an entity object instance is successfully modified in the entity cache (after it acquires a lock), all view object instances who have queried data for the row are notified so clients can refresh their displayed data.

After locking a row, the entity object verifies that the data retrieved from the database during the lock acquisition matches the unmodified data in the entity cache. If not, an oracle.jbo.RowInconsistException is thrown. This ensures that if another program in another database session has modified that row, then the application does not unknowingly overwrite that modification.

How database data is brought into an existing cache

There are different data resolution processes for locking, fault-in, and updates.

For locking, here is how the business logic tier resolves the data:

If there are no differences, the data from the database is placed in the cache. If there are differences, a RowInconsistException is thrown. You can catch the exception and resolve the differences in your code.

For example, let's say there is $100 in a joint checking account and the two owners go to an ATM:

  1. Person1 checks the balance and sees $100, populating the entity cache.
  2. Person1 requests a $20 withdrawal.
  3. Person2 checks the balance, and sees $100, populating a different entity cache.
  4. Person1's transaction gets a lock, calls setAttribute to set the balance to $80, and commits the transaction.
  5. Person2 elects to withdraw $50.
    A RowInconsistException is thrown, because the values in the entity cache have changed.

For fault-in, if the business logic tier has modified an attribute in the cache, the modified value is kept. If the business logic tier has not modified the value, the value retrieved from the database is used.

For updates, the business logic tier locks before the update and compares values, so it knows immediately if data has changed.

The commit and rollback cycle

Associated with the top-level application module is a transaction object. This object manages the interaction between the entity cache and database. To do this, the transaction object keeps a transaction list, which is a list of entity object instances that have been changed during this transaction.

In general, the commit and rollback cycle follows these steps:

  1. When the client calls Transaction postChanges or commit, the transaction object iterates through the transaction list and calls the doDML method on each entity object, to perform delete, data update, and insert operations in the database.

    Note that commit calls postChanges.

    In general, the transaction object processes entity objects in the transaction list in the chronological order of when the entity object was first modified in this transaction. However, there is no guaranteed order that changes are processed. For a composition, however, the transaction object processes the parent entity object first, followed by children entity objects.

  2. After the posts are complete, the transaction list cleared.

  3. The data is committed or rolled back in the database.

  4. The locks are released.

The order that you insert rows in a database matters for associations. For example, suppose you have an association between Employees and Departments, with a key of Department_Id that associates the two entity objects. First, you create an employee with a Department_Id of a department not yet created. Next you create a department for that new Department_Id. If you post these changes, the transaction object will create the new employee first (with the new Department_Id), followed by the department. If the database had a foreign key referential integrity constraint on the Employees table, you will get a database error. This is because the transaction object tried to create a row in the Employees table where the associated department had not yet been created. This situation is a post order problem: associated rows were not created in the proper order.

One way to avoid post order problems is to use a composition. In a composition, the transaction object ensures that the parent entity object is written to database first, followed by children. In the Employees/Departments example, if you marked the association as a composition, Departments being the parent and Employees the child, then the new department is created first, so you would not get a referential integrity error.

Only valid rows are posted. The entity object makes sure each row is valid before posting.

How rows are deleted

Here are the general steps for row removal:
  1. The client removes a row by either calling the ViewRow remove method or RowSetIterator removeCurrentRow method.

    Note that the second approach causes the remove method to be called on the current row of the row set iterator.

  2. For both pessimistic and optimistic locking, the business logic tier locks the entity rows that comprise the view row. Next, it calls the EntityImpl remove method.

    This call marks the entity row as deleted and sends out an event that marks all view rows that use the entity row as "dead." The dead view rows are removed from the view cache.

  3. When the user calls the postChanges (or commit) method, for each entity row marked as deleted, the corresponding database row is deleted.

  4. When the transaction is committed, the entity row is marked as dead and is removed from the cache.

When rows are removed from a cache

When a view object or application module is destroyed, the associated view caches are also destroyed. You can also explicitly clear view caches by calling certain methods, including clearVOCaches on the application module, clearEntityCache on the transaction, and clearCache on the view object.

When no view caches point to an instance in the entity cache, and the entity instance hasn't been modified, the instance becomes a candidate for clearing. When the rows are cleared depends on the Java VM you are using. The Java VM usually removes them when it needs more memory.

Refreshing the caches

Business Components for Java provides methods that can clear the cache. These methods are mainly used for these two purposes:

You can use the following methods to clear the caches:

These last two groups of methods set (or inquire about) the flag that controls whether the entity cache is automatically cleared after commit or rollback. For example, if setClearCacheOnCommit(true) is called on a transaction, whenever the transaction is committed, all entity caches are cleared. Subsequent access to entity objects will refresh data from the database.

Note that by default, isClearCacheOnCommit is false and isClearCacheOnRollback is true. So, by default, the entity caches are cleared after rollback. But after commit, the entity caches are retained (not cleared).