Example: Explicit invalidation in the persistence manager cache
Usage Scenario
The scenario of use for this feature begins with configuring one or more bean types to be long-lifetime beans (see Explicit Invalidation in the Persistence Manager Cache, and configuring the necessary Java Message Service (JMS) resources (described below). Once this is done, the server is started. The scenario continues as follows:
- Assume that a CMP entity bean of type Department has been configured to be a long-lifetime bean.
- Transaction 1 begins. Application code looks up Department's home and calls a finder method (such as findByPrimaryKey("dept01") ). As this is the first finder to return Department dept01, a trip is made to the database to obtain the data. Transaction 1 ends.
- Transaction 2 begins. Application code calls findByPrimaryKey("dept01") again. Because this is not the first finder to return Department dept01, we get a cache hit and no database trip is made. Transaction 2 ends. So far this is current WAS behavior for long-lifetime beans.
- Another application, which does not use the Department CMP bean, is executed. This application might or might not be run on WebSphere Application Server; it could be a legacy application. The application updates the database table that is mapped to the Department bean, altering the row for dept01. For example, the budget column in the table (mapped to a Java double CMP attribute in the Department bean) is changed from $10,000.00 to $50,000.00. This application was designed to cooperate with WebSphere Application Server. After performing the update, the application sends an invalidate request message to invalidate the (now incorrect) cached data for Department bean dept01.
- Transaction 3 begins. Application code looks up Department's home and calls a finder method (such as findByPrimaryKey("dept01") ). Because this is the first finder after Department dept01 is invalidated, a new database trip is made to obtain the data. Transaction 3 ends.
Persistence Manager cache invalidation API
The PM cache invalidation API is in the form of a JMS message that the client sends to a specially-named JMS topic using a connection from a specifically named JMS TopicConnectionFactory. The JMS message must be an ObjectMessage created by the client. The client code creates a PMCacheInvalidationRequest object that describes the bean data to invalidate. Client code places the PMCacheInvalidationRequest object in the ObjectMessage and publishes the ObjectMessage (for further details on the JMS objects and terms used here, please see the Java Message Service documentation).
The public class PMCacheInvalidationRequest is central to the API, so we include a portion of its code here for illustration purposes (if you see any differences between this illustration and the actual class, the class is to be considered correct):
packagecom.ibm.websphere.ejbpersistence; /** *An instance of this class represents a request to invalidate one or more *CMP beans in the PM cache. When an invalidate occurs, cached data for this *bean is removed from the cache; the next time an application tries to find *this bean, a fresh copy of the bean data is obtained from the data store. * *The ability to invalidate a bean means that a CMP bean may be configured *as a long-lifetime bean and thus be cached across transactions for much *greater performance on future attempts to find this bean. Yet when some *outside mechanism updates the bean data, sending an invalidation request *will remove stale data from the PM cache so applications do not behave falsely *based on stale data. */ public class PMCacheInvalidationRequestimplementsSerializable{ . . . /** * Constructor used to invalidate a single bean * @param beanHomeJNDIName the JNDI name of the bean home. This is the same value * used to look up the bean home prior to calling findByPrimaryKey, for example. * @param beanKey the primary key of the bean to be invalidated. The actual * object type must be the primary key type for this bean type. */ public PMCacheInvalidationRequest(String beanHomeJNDIName, Object beanKey) throws IOException { . . . } /** * Constructor used to invalidate a Collection of beans * @param beanHomeJNDIName java.lang.String the JNDI name of the bean home. * This is the same value used to look up the bean home prior to calling * findByPrimaryKey, for example. * @param beanKeys a Collection of the primary keys of the beans to be * invalidated. The actual type of each object in the Collection must be the * primary key type for this bean type. */ public PMCacheInvalidationRequest(String beanHomeJNDIName, Collection beanKeys) throws IOException { . . . } /** * Constructor used to invalidate all beans of a given type * @param beanHomeJNDIName java.lang.String the JNDI name of the bean home. * This is the same value used to look up the bean home prior to calling * findByPrimaryKey, for example. */ public PMCacheInvalidationRequest(String beanHomeJNDIName) { . . . } }If the client wants to perform the invalidation in a synchronous way, it can opt to receive an acknowledgement JMS message when the invalidation is complete. To ask for an acknowledgement (ACK) message, the client sets a Topic of its own choosing in the JMSReplyTo field of the ObjectMessage for the invalidation request (see the Java Message Service documentation for further details). The client then waits (using the receive() method of JMS) on receipt of the acknowledgement message before continuing execution.
An ACK message enables the caller to insure there is not even a brief (seconds or less) window during which PM cache data is stale. The sending of an acknowledgement for each request does, of course, take a bit more time and so is recommended to be used only when needed.
The JMS resources used to make an invalidation request -- topic connection factory, topic destination (sometimes called just "topic", and so forth -- must be configured by the user (using the administrative console or other method) if they want to use PM Cache Invalidation. In this way the user can chose whichever JMS provider they prefer (as long as it supports pub-sub). The names that must be used for these resources are defined as part of the API. These names are unique to the WAS namespace to avoid name conflict with customer JMS resources.
The following are the names that must be used when the user configures the JMS resources (shown as Java constants for clarity):
// The JNDI name of the topic connection factory private static final String topicConnectionFactoryJNDIName = "com.ibm.websphere.ejbpersistence.InvalidateTCF"; // The JNDI name of the topic destination private static final String topicDestinationJNDIName = "com.ibm.websphere.ejbpersistence.invalidate"; // The topic name (part of the topic destination) private static final String topicString = "com.ibm.websphere.ejbpersistence.invalidate";Other JMS configuration, such as bus name (required if you choose the default messaging JMS provider), can use names you define yourself. Also, the bus used by the invalidate JMS resources can be used by other resources; the invalidate mechanism does not require exclusive use of a bus.
Here are examples of how these constants can be used in client code:
// Look up the topic connection factory... InitialContext ic = new InitialContext(); TopicConnectionFactory topicConnectionFactory = (TopicConnectionFactory) ic.lookup(topicConnectionFactoryJNDIName); ... // Look up the topic Topic topic = (Topic) ic.lookup(topicDestinationJNDIName);Note that JMS messages can be sent not only from J2EE application code (for example, a SessionBean or BMP entity bean method) but also from non-J2EE applications if your chosen JMS provider allows for this. For example, the IBM MQ Client product installed on a database server, which typically does not have J2EE installed to create a topic connection and topic that are compatible with the topic connection factory and topic destination you configure using the WAS administrative console.