11.3 Transactions in JDO


Earlier in this chapter, we briefly mentioned that, in a flat transaction model, the calling thread is associated with a transaction. In JDO, the PersistenceManager represents the application's view of any persistent data. Logically, it is here that this association must occur. To separate out the functionality from the PersistenceManager itself, JDO defines a javax.jdo.Transaction interface. This Transaction has a one-to-one relationship with the PersistenceManager, meaning that if parallel transaction execution is required in an application, then multiple persistence managers must be obtained from the factory. (Refer to Chapter 1 for a discussion of transient and persistence instances.)

The Transaction interface by itself is quite simple, and the class diagram is shown in Figure 11-4. It contains a few different get and set methods that can be used to access and set different properties of the Transaction instance. These methods are discussed in subsequent sections. The begin() , commit() , and rollback() methods are the primary methods that application code uses to demarcate the transaction.

Figure 11-4. The javax.jdo.Transaction interface.

graphics/11fig04.gif

Any persistence- related work or interaction with persistent objects done by the application must be performed in the context of a JDO transaction as shown below:

 
 PersistenceManager pm = pmf.getPersistenceManager();   Transaction tx = pm.currentTransaction();   tx.begin(); // Do some work here with persistent objects    tx.commit(); // OR tx.rollback() 

To preserve isolation semantics (the "I" in the ACID), a transaction "locks" the data in the underlying datastore using different concurrency control techniques. The locking, which happens under the covers and is completely transparent to application code, ensures that multiple operations on the same dataset do not interfere with each other. This is analogous to the way the synchronized keyword operates in the Java language. By using different locks (e.g., read locks, write locks, and so on), the transaction ensures that the serializable characteristic or the appearance that the transactions are acting upon the dataset one after the other is maintained .

The Four Isolation Levels

Most resource managers allow four isolation levels for a transaction, which are listed in increasing order of strictness:

  • Read uncommitted: If another concurrently executing transaction writes data to the underlying resource without committing it, the data is visible in the executing transaction.

  • Read committed: Committed data from another concurrently executing transaction is available.

  • Repeatable read: Data read, which has been committed by another concurrently executing transaction, is guaranteed to retain the same values when read again.

  • Serializable: This guarantees that two transactions operating on the same data execute serially one after the other.


J2EE and Isolation

The only way to specify isolation levels in J2EE (including EJBs) is by using a resource manager-specific API such as the JDBC Connection API. Most relational databases, by default, utilize the repeatable read or serializable isolation levels. Serializable isolation solves the problem of unrepeatable reads (application reads data and works on it, but when re-read, the dataset is different because of modification by another concurrently executing transaction) and phantom reads (application reads dataset, but when re-read, there is an addition to the dataset due to modifications by another concurrently executing transaction), but it can degrade performance due to the large number of locks required.


Based on the locking strategy and isolation level used, a transaction can be categorized into two groups:

  • Pessimistic transactions: The data that is being used in this transaction is locked in the underlying resource and cannot be modified by anyone until the transaction has completed. This corresponds to the serializable isolation level and is also the strategy that the EJB model assumes. Pessimistic transactions are the default model for JDO that all vendors must implement. When a transaction executes, the data reflected in the persistent objects is locked by the underlying datastore.

     
     PersistenceManager pm = pmf.getPersistenceManager(); Transaction tx = pm.currentTransaction(); tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); // do some other work here // the data in the datstore that represents the author // object is locked tx.commit() // OR tx.rollback() 
  • Optimistic transaction: This model assumes that it is unlikely for the data being used to be modified by any other transaction in the application. If at commit time, the datastore detects a collision, then the transaction is rolled back. This is an optional strategy for JDO vendors to implement. The JDO transaction strategy can be specified using the setOptimistic(boolean optimistic) method. When invoked with a true , the transaction is set to be optimistic; a false results in the default pessimistic strategy.

    To understand this further, look at the traditional producer-consumer example built on the code introduced in Chapters 3 and 5. The "hungry" consumer shown in Listing 11-1 runs in an infinite loop and searches the datastore for a particular object, and the transaction type is set with an argument. The producer creates the object instance, saves it, and reads the data back instantly as shown in Listing 11-2.

Listing 11-1 The hungry consumer
 import com.corejdo.examples.chapter4.model.Author; import javax.jdo.*; import java.util.*; import java.io.FileInputStream; public class Consumer {   public static void main (String args[]) throws Exception{     boolean txtype= new Boolean(args[0]).booleanValue();     Properties p = new Properties();     p.load(new FileInputStream("config.properties"));     PersistenceManagerFactory pmf =             JDOHelper.getPersistenceManagerFactory( p );     PersistenceManager pm = pmf.getPersistenceManager();     System.out.println("Reader searching..");     while(true){       Transaction tx = pm.currentTransaction();       tx.setOptimistic(txtype);       tx.begin();       Query query = pm.newQuery(Author.class);       String filter = "name ==(\"Keiron McCammon\")";       query.setFilter(filter);       Collection authors =(Collection) query.execute();       Iterator it = authors.iterator();       while (it.hasNext()) {              Author author = (Author) it.next();              String name = author.getName();              System.out.println("Author name = " +name);              }            tx.commit() ;       }   } } 
Listing 11-2 The producer
 import com.corejdo.examples.chapter4.model.Author; import javax.jdo.*; import java.util.*; import java.io.FileInputStream; public class Producer {   public static void main(String[] args) throws Exception {   boolean txtype= new Boolean(args[0]).booleanValue();   Properties p = new Properties();   p.load(new FileInputStream("config.properties"));   PersistenceManagerFactory pmf =           JDOHelper.getPersistenceManagerFactory( p );   PersistenceManager pm = pmf.getPersistenceManager(); // create and persist the object   Transaction tx = pm.currentTransaction();  tx.setOptimistic(txtype);  tx.begin();   Author author = new Author("Keiron McCammon");   pm.makePersistent(author);   System.out.println("Author persisted");   tx.commit() ; // Read the persisted object from the datastore   tx.begin() ;   Query query = pm.newQuery(Author.class);   String filter = "name ==(\"Keiron McCammon\")";   query.setFilter(filter);   Collection authors = (Collection) query.execute();   System.out.println("Everything is locked"); // do some other work here   Thread.sleep(120000); // the data in the datstore that represents the author   System.out.println("Producer wokeup");   tx.commit() ;   System.out.println("Producer closed");   pm.close();     } } 

To execute the example, the steps outlined in Chapter 3 need to be followed (i.e., the classes compiled and enhanced, and the datastore set up for the implementation being used) and then the consumer started, followed by the producer.

With pessimistic or datastore transactions, the producer locks the data corresponding to the persistent objects in the underlying datastore, making it inaccessible to other transactions. The consumer can only access the data in the window of time between the two transactions in the producer or when the producer exits. This is shown in Figures 11-5a and 11-5b.

Figure 11-5a and b. The producer has locked the data in pessimistic transaction, and the consumer is waiting for the locks to be released.

graphics/11fig05.jpg

With optimistic transactions, the producer doesn't lock the data for the duration of the transaction, so the consumer is able to access the data immediately after it is persisted, as shown in Figures 11-6a and 11-6b.

Figure 11-6a and b. The optimistic producer does not lock the data, and the consumer can access it immediately.

graphics/11fig06.jpg

There are distinct advantages and disadvantages to each locking technique. Tables 11-4 and 11-5 summarize the differences and approaches to use.

Table 11-4. Pessimistic Transactions Summarized

Strategy

Advantages

Disadvantages

Pessimistic: Data is locked until the transaction completes.

Provides highest degree of consistency in the data.

Does not scale well.

May cause deadlocks when transactions are waiting on each other to release locks.

May be detrimental to performance because of the high degree of locking.

When to use

When concurrent access is rare or not expected.

When transactions are short lived.

When user think-time does not affect transaction duration.

When not to use

For long lived transactions.

If optimistic transactions suffice (see Table 11-5).

Table 11-5. Optimistic Transactions Summarized

Strategy

Advantages

Disadvantages

Optimistic: Data is locked only when the transaction committed.

Performs better because locking overhead is reduced.

Systems generally scale better because resource usage at the datastore level is reduced.

Requires developers to write code to support collision detection and handling such situations.

When to use

When concurrent access is expected to be frequent and high.

When transactions are long lived.

When the clients are remote (e.g., the JDO code on thick clients ).

When transaction involve user think-time.

When not to use

For long lived transactions.

11.3.1 Understanding conflicts with optimistic transactions

Although optimistic transactions assume that it is unlikely for the underlying data being used to be modified, such situations can occur. To understand how to resolve such potential conflicts, consider the scenario in Figure 11-7, where two overlapping transactions, Transaction A and Transaction B, affect the same underlying data.

Figure 11-7. Conflicts in optimistic transactions.

graphics/11fig07.gif

When transaction B attempts to commit, its changes at time t2 would be forced to rollback because the underlying data has been changed externally by Transaction A at t1 (the datastore detects a collision). This is often called "First Commit Wins," in which the first set of changes are not overwritten and force the second transaction to restart the work with the changed data. The exception facilitates failure detection; however, the amount of rework is increased because developers now have to explicitly account for such situations and retry Transaction B and the business logic by reloading the data from the datastore.

One strategy for reducing the occurrence of such conflicts is called "Last Commit Wins," which assumes that the consequences of overwriting data are acceptable to the application.

This strategy tries to reduce the risk of overwrites by refreshing the data from the underlying datastore in Transaction B just prior to performing the business logic. In JDO, this is achieved though the refresh() method in the PersistenceManager, which reloads the state of a particular persistent object from the datastore. For example, in the code below, the author instance is refreshed explicitly in an optimistic transaction:

 
 PersistenceManager pm = pmf.getPersistenceManager();   Transaction tx = pm.currentTransaction();   tx.setOptimistic(true); tx.begin(); try { //     // do some work here     //          Author author = (Author) it.next();          String name = author.getName(); // Just before changing the persitent object refresh it  pm.refresh(author);  author.setName("John Malkovich");          tx.commit();          }catch(Exception e){                     if(tx.isActive())                      tx.rollback();          } 

11.3.2 Transactional and non-transactional objects

In Chapter 3, we mentioned how the PersistenceManager includes a caching mechanism, and in Chapter 4, we looked at the different states including transactional and non-transactional states.

You may recall that objects can be categorized as transactional and non-transactional depending on the caching behavior of the PersistenceManager. This is orthogonal to the concept of transient and persistent objects introduced in Chapter 1 and later covered as states in Chapter 4. A transactional object is an object whose managed fields are cached by the underlying implementation of the PersistenceManager in the context of a Transaction. For example, consider the code below:

 
 PersistenceManager pm = pmf.getPersistenceManager(); Transaction tx = pm.currentTransaction(); tx.begin(); Author author = new Author("Keiron McCammon"); try {     author.setName("John Malkovich");   // do some other work here     tx.commit()     }catch(Exception e){        tx.rollback();       } 

Persistent Fields and Transactional Fields

Object instance variables that are persisted are called persistent fields. Instance variables that participate in a transaction are called transactional fields, meaning that their values can be restored by a rollback. Both persistent and transactional fields are collectively called as managed fields because they must be managed by the JDO implementation.

Recall from Chapter 5 that individual fields can be marked as persistent or transactional in the metadata descriptor. By default, fields that are not static, final, or transient are marked as "persistent" and exhibit transactional behavior in the enhanced class. When marked as "transactional," the fields are cached and restored on a transactional rollback, but are not persisted to the datastore.


By default, the name field is transactional because the Author class is persistence-capable, and the value "Keiron McCammon" is cached in the PersistenceManagers cache. This value is restored in case of a potential rollback() and is discarded when commit() is invoked.

All this is unrelated to the fact that the Author object is transient or persistent. In this example, the instance is persistent and undergoes a state change to persistent-dirty when the setName() method is invoked. However, author could be an instance of a transient object as well, in which case the state changes would be different, as explained in Chapter 5.

In summary, both transient and persistent objects can be transactional or non-transactional. JDO requires that implementations support transactional behavior for persistent objects. A transient transactional object is one that by itself does not represent persistent data, but has fields that recognize transactional boundaries and are restored to their original values by the JDO implementation when the transaction rolls back. Support for transactional behavior in transient objects is optional, meaning that a JDO implementation is not required to cache fields for transient objects.

This is in line with the general behavior in which, between the scope of a tx.begin() and a tx.commit() , transient objects ”i.e., persistence-capable instances that are made transient using the pm.makeTransient() ”are not managed by the JDO implementation. Therefore, by default, transient instances are non-transactional, whereas persistent instances are managed by the JDO implementation and are transactional.

Transient Objects and State Transitions

A transient object is merely an instance that is not persisted; however, the class definition must still be persistence-capable in order for JDO to manage the state transitions.


Table 11-6. Transactional Behavior in Persistent and Transient Objects
 

Transactional

Non-Transactional

Persistent Objects

Required

Optional

Transient Objects

Optional

Required (JVM's default behavior)

The persistence manager can be configured with two optional properties that specify how non-transactional instances are accessed and can be very helpful while using optimistic transactions:

  • javax.jdo.option.NontransactionalWrite : When this property is set to true , the fields of a persistent non-transactional instance can be modified outside the scope of a JDO transaction, even though changes made in this manner are not automatically propagated to the datastore. Without this property explicitly sent, the default behavior is to throw a JDOUserException for any attempt to modify a persistent instance outside the scope of a JDO transaction.

  • javax.jdo.option.NontransactionalRead : When this property is set to true , the fields of a non-transactional instance can be read outside the transaction.

The usage of both of these properties can be better understood by the example in Listing 11-3. The properties file passed to the JDOHelper must contain the above properties set to true , and the implementation must support this behavior, which is optional in the specifications. The example first creates and saves an object. It then uses the NonTrasnactionalRead property to read it outside the JDO transaction and the NonTrasnactionalWrite property to modify the object. The example also shows transactional behavior of transient and persistent instances.

Listing 11-3 A non-transactional read-write example
[View full width]
 import com.corejdo.examples.chapter4.model.Author; import javax.jdo.*; import java.util.*; import java.io.FileInputStream; public class NonTxReadWriteExample {  public static void main(String[] args) throws Exception{ // The properties file contains all configuration // properties     Properties p = new Properties();     p.load(new FileInputStream("config.properties"));     PersistenceManagerFactory pmf =  JDOHelper.getPersistenceManagerFactory( p );     PersistenceManager pm = pmf.getPersistenceManager(); // create and persist the object     Transaction tx = pm.currentTransaction();     tx.setOptimistic(true);     tx.begin();     Author author = new Author("Keiron McCammon");     pm.makePersistent(author);     System.out.println("Author persisted");     tx.commit() ; // At this point the data is commited to datastore. // Note Below line with throw javax.jdo.JDOUserException // if implementation does not graphics/ccc.gif support property // javax.jdo.option.NontransactionalRead=true    System.out.println("Author Name = "+author.getName()); // Persisted data of Keiron above will not be be // overwitten by this even though the instance is modified // and available in memory graphics/ccc.gif while this application is // executing. // Note :Below line with throw javax.jdo.JDOUserException // if implementation does not graphics/ccc.gif support property // javax.jdo.option.NontransactionalWrite=true    author.setName("Heiko Bobzin"); // Create another object. This is transient   Author contributingauthor = new Author("John Malkovich");   tx.begin(); // Transient-Transactional    pm.makeTransactional(contributingauthor); // make some changes to object    contributingauthor.setName("Jeanne Smith"); // objects state will be restored from cache    tx.rollback(); // should not print out "Jeanne Smith" but initial cached // value of "John Malkovich"   System.out.println("Contributing Author Name = "   +contributingauthor.getName()); // the segment below shows trasnactional behavior in the // persistent objects.     tx.begin();     Author anotherauthor = new Author("John Doe"); // instance is transient     pm.makePersistent(anotherauthor); / instance is now persistent     anotherauthor.setName("Michael Vorburger");     System.out.println("Author name inside tx=" +anotherauthor.getName());     tx.rollback() ; // The cached value of John Doe is replaced in the // persistent object.     System.out.println("Author name outside tx=" +anotherauthor.getName()); // Read the persisted object from the datastore   Query query = pm.newQuery(Author.class);   Collection authors = (Collection) query.execute();   Iterator it = authors.iterator();   while (it.hasNext()) {      author = (Author) it.next();      String name = author.getName();      System.out.println("Author name = " + name);      }   } } 

11.3.3 Retaining and restoring values

The PersistenceManager also allows two optional settings that affect the manner in which instances are kept in memory at commit and rollback time:

  • javax.jdo.option.RetainValues : By default, after a transaction commits, the persistent instances in the cache are evicted and moved to a hollow state as explained in Chapter 4. This allows the implementation to easily manage the cache by keeping it small. When this property is set to true , the instances are retained in the cache after a transaction commits. This can increase the cache size, but can be helpful if the same instances are reused across transactions in an application.

  • javax.jdo.option.RestoreValues : By setting this property to false , the default transactional behavior of restoring fields to the state that they were in when the makePersistent() method was invoked can be overridden. Application code needs to explicitly restore the original values. Because the overhead of the cache is removed, the performance is generally improved with this property. This should only be set if instances are not reused after a transaction rolls back. The code segment below from the previous example explains this further:

     
    [View full width]
     
    [View full width]
    Transaction tx = pm.currentTransaction(); // the segment below shows trasnactional behavior in the // persistent objects. tx.begin(); Author anotherauthor = new Author("John Doe"); pm.makePersistent(anotherauthor); anotherauthor.setName("Michael Vorburger"); System.out.println("Author name inside tx=" +anotherauthor.getName()); tx.rollback() ; // With javax.jdo.option.RestoreValues=false this will not // restore the value to "John graphics/ccc.gif Doe" since that was never // cached and the value "Michael Vorburger" will be printed System.out.println("Author name outside tx=" +anotherauthor.getName());

RetainValues and RestoreValues

The optional features RetainValues and RestoreValues affect only how the instances are kept in memory at commit and rollback time, respectively. They do not alter the outcome of the commit or rollback on a transaction. The RestoreValues option is used if instances are not used across rollbacks , and the RetainValues option is used if instances are used across transactions.


11.3.4 JDO and transactions in a J2EE application server

In order to incorporate transactional semantics in J2EE components that utilize JDO for transparent persistence, it is important to understand how a transaction in JDO behaves in such managed environments.

As mentioned in Chapters 1 and 8, the JDO specifications define the relationship and integration between the JDO and the managed J2EE environment by requiring the vendors to satisfy the connector (Java Connector Architecture, or JCA) contracts. In short, the JDO implementation is integrated into the J2EE container as a JCA connector (i.e., a RAR file). The components use JDO by obtaining the PersistenceManagerFactory from a JNDI tree by name and then use it to obtain the PersistenceManager as usual. Figure 11-8 summarizes the transactional aspect of this integration. Although JDO allows the implementation to provide its own adapter or to use third-party adapters, most vendor implementations today provide their own adapters and package their implementations as RAR files for deployment in different application servers.

Figure 11-8. JDO and J2EE transactional integration.

graphics/11fig08.gif

In order to connect to the resource, the adapter can manage its own connections or have them managed by the container. Details about connection management and JCA integration are covered further in Chapter 8. In this section, we concern ourselves with transactional aspect for now. In JCA, a resource adapter can be classified in three distinct levels that inform the container about its transactional capabilities. These are summarized in Table 11-7. In order for the application to use distributed transaction capabilities in managed environments, not only must the underlying datastore expose the XAResouce (e.g., the JDBC driver must support the javax.sql.XAConnection ), but also this must be exposed at the resource adapter level so that the TransactionManager in the container can manage the distributed transaction.

Table 11-7. Transactional Behavior for Resource Adapters

Transaction Level of Connector

Work Performed as Part of JTA Transaction

Distributed Transactions or Combination with Other Transactional Resources Allowed

NoTransaction

LocalTransaction

XATransaction

At the simplest level, components such as Servlets and JSPs can use JDO and the javax.jdo.Transaction in much the same way as the standalone code discussed until now to support local transactions. Rather than using the JDOHelper , the PersistenceManagerFactory is obtained from the JNDI tree. The example in Listing 11-4 shows a Servlet that obtains the PersistenceManagerFactory from JNDI at initialization time. The PersistenceManager obtained from this factory is then used for persisting data while servicing client requests . If the implementation supports pooling, the close() method returns the PersistenceManager instance back to the pool.

JNDI Binding

So show does the factory get bound by name in JNDI? The JCA adapter is required to provide the javax.resource.spi.ManagedConnectionFactory interface. This represents the outbound connectivity information to the EIS instance from an application via the resource adapter instance and includes. Along with this factory, the adapter specifies the interface supported by this factory and the implementation class as a part of the adapter's ra.xml configuration. For example:

 
 <resourceadapter> <managedconnectionfactory-class>      some vendor class </managedconnectionfactory-class> <connectionfactory-interface>            javax.jdo.PersistenceManagerFactory                        </connectionfactory-interface> <connectionfactory-impl-class>        some vendor class </connectionfactory-impl-class> <connection-interface>      javax.jdo.PersistenceManager</connection-interface> <connection-impl-class>        some vendor class </connection-impl-class> <!--other JCA elements -- > </resourceadapter> 

When the adapter is deployed, it is bound to a JNDI name in an application server-specific manner like the deploytool in the J2EE reference implementation. When application code looks up JNDI by that name, based on the ra.xml file, the container returns a PersistenceManagerFactory.


Listing 11-4 A Servlet using JDO and local transactions
 import javax.servlet.*; import javax.servlet.http.*; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.jdo.*; import java.util.Collection; import java.util.Iterator; import com.corejdo.examples.chapter4.model.Author; public class JDOServlet extends HttpServlet {     PersistenceManagerFactory pmf;     public void init(ServletConfig config) throws ServletException {         try {             super.init(config);             InitialContext ctx = new InitialContext();             pmf = (PersistenceManagerFactory) ctx.lookup("JDOPMFactory");         } catch (NamingException e) {             throw new ServletException(e);         }     }     /** Processes requests for both HTTP <code>GET</code>         and <code>POST</code> methods.      */     protected void service(HttpServletRequest request, HttpServletResponse response)             throws ServletException, java.io.IOException {         response.setContentType("text/html");         java.io.PrintWriter out = response.getWriter();         String authorname = request.getParameter("authorname");         if (authorname == null) {             out.println("Invalid Request");             out.close();             return;         }         PersistenceManager pm = pmf.getPersistenceManager();         Transaction tx = pm.currentTransaction();         tx.begin();         Author author = new Author("authorname");         pm.makePersistent(author);         tx.commit();         out.println("<html><body>");         out.println("<h2>Authors in this Example are</h2>");         // Read the persisted object from the datastore         Query query = pm.newQuery(Author.class);         Collection authors = (Collection) query.execute();         Iterator it = authors.iterator();         while (it.hasNext()) {             author = (Author) it.next();             out.println("Author name = " + author.getName());         }         pm.close();         out.println("</body></html>");         out.close();     } } 

Although the above usage of transactions is suitable for simple applications, applications using multiple components together in a single transaction or requiring distributed transactions require more. In J2EE, the container makes the JTA UserTransaction available to components like Servlets or JSPs using JDNI and to EJBs using the EJBContext object (EJBs can invoke the getUserTransaction() method). When the UserTransaction is used to demarcate the transaction, a mechanism is needed to inform the JDO implementation to flush its cache and the datastore and to synchronize itself with the transaction. For this purpose, JDO specifications require that the vendor implement the javax.transaction.Synchronization interface in the Transaction object.

The container is responsible for invoking the beforeCompletion() and afterCompletion() callbacks in the Synchronization listeners (the JDO implementation in this case) when the transaction completes. This is analogous to the way javax.ejb.SessionSynchronization works in EJBs.

When the transaction is demarcated using the UserTransaction , the PersistenceManager should be obtained inside the transaction. For example:

 
 UserTransaction utx=                  ctx.lookup("java:comp/UserTransaction") ; tx.begin() ; // do some work PersistenceManager pm=factory.getPersistenceManager(); // do some JDO work here pm.close(); tx.commit() ;    } 

In situations like the above, when the persistence manager is obtained inside an active JTA transaction, using methods of the JDO transaction throws a JDOUserException . This is done to allow the implementation to synchronize itself with the JTA transaction. Therefore, when demarcating transactions using the UserTrasnaction as above or when using container-managed transactions, you should not use the JDO Transaction methods.

Transaction Boundaries in EJBs

In bean-managed transactions, the EJB code uses the UserTransaction object to demarcate the transactions boundaries explicitly using begin() and commit() . For EJBs utilizing container-managed transactions, the container (not the bean code) sets the boundaries of the transactions. Container-managed transactions can be used with session, entity, and message-driven beans, while bean-managed transactions cannot be used with entity beans.


Although it is rare for Servlets and JSPs to use JDO and other JDBC (or even multiple JDO implementations in the same container) collectively as a part of a single transaction, it may be common for JDO and JMS to be used together. Both of these cases would require additional care. For example, consider the example in which the service method of a Servlet contained code that interacts with some pre-existing databases or EIS resources and JDO in the same invocation.

 
 protected void service(HttpServletRequest request,                           HttpServletResponse response){ // do some JDO persistence work here // update an existing JDBC or EIS resource or put some // messages on a JMS topic/queue    } 

Such interactions with multiple datastores would need the services of a JTA transaction manager and a UserTransaction to coordinate the distributed transaction.

 
 protected void service(HttpServletRequest request,                           HttpServletResponse response){ // get the JTA Transaction UserTransaction utx=                  ctx.lookup("java:comp/UserTransaction") ; tx.begin() ; // do some JDO work here // update an existing JDBC or EIS resource tx.commit() ;    } 

Let's explore this further with an example of an EJB designed for bean-managed persistence and container-managed transactions utilizing distributed transaction capabilities. Listing 11-5a shows the remote interface with an updateAuthors method, and Listing 11-5b shows the home interface. The session bean is shown in Listing 11-5c and contains the implementation for updateAuthors . The BMP EJB uses JDO to save the Author objects and also updates a table in an existing ISBN database with the new author names . This EJB does not use JDO or JTA transactions explicitly, but uses container-demarcated transactions as a part of its deployment descriptor. For example, the ejb-jar.xml descriptor contains something like this:

 
 <ejb-jar> <!--other elements here-->   <container-transaction>       <method>         <ejb-name>TxEJB</ejb-name>         <method-name>*</method-name>       </method>       <trans-attribute>Required</trans-attribute>     </container-transaction>   </assembly-descriptor> </ejb-jar> 

Non Transactional Resources

Using transactions makes sense only if the underlying resource is transactional. For example, including an action such as sending an email as part of a transaction or declaring an EJB that sends mail as transactional merely causes overheads.


Listing 11-5a The remote interface
 package com.corejdo.examples.transactions; import java.rmi.RemoteException; import java.util.Vector; import javax.ejb.*; public interface AuthorEJB extends javax.ejb.EJBObject {  public boolean updateAuthors(Vector authors, String isbn)            throws RemoteException, EJBException; } 
Listing 11-5b The home interface
 package com.corejdo.examples.transactions; import java.rmi.RemoteException; import javax.ejb.*; public interface AuthorHome extends javax.ejb.EJBHome {  public AuthorEJB create() throws CreateException,               EJBException, RemoteException; } 
Listing 11-5c The EJB implementation using JDO and JDBC together
[View full width]
 package com.corejdo.examples.transactions; import com.corejdo.examples.chapter4.model.Author; import javax.jdo.*; import java.rmi.RemoteException; import java.util.Vector; import javax.naming.InitialContext; import javax.naming.NamingException; import java.sql.Connection; import javax.sql.DataSource; import javax.ejb.*; import java.util.Iterator; import java.sql.PreparedStatement; public class AuthorBean implements javax.ejb.SessionBean {     private javax.ejb.SessionContext ctx;     private PersistenceManagerFactory pmf;     private InitialContext ictx;     private static String AuthorXADataSource = "AuthorXADataSource";     public void setSessionContext(javax.ejb.SessionContext context) throws RemoteException graphics/ccc.gif , javax.ejb.EJBException {         ctx = context;         try {             ictx = new InitialContext();             pmf = (PersistenceManagerFactory)ictx.lookup("java:comp/env/jdo/authorsb");     System.out.println("AuthorBean : Located PMF jdo/authorsb");         } catch (NamingException e) {            System.out.println("AuthorBean : Exception locating PMF jdo/authorsb " + e);             throw new EJBException(e);         }     }   public void ejbActivate() throws EJBException{}   public void ejbPassivate() throws EJBException{}   public void ejbRemove() throws EJBException {}   public void ejbCreate() throws CreateException,                                   EJBException {}    /**      * @input A vector of Author objects      * @output The ISBN number      */  public boolean updateAuthors(Vector authors, String isbn){ // initialize member variables     Connection con=null;     String authornames="";     PreparedStatement ps =null;     PersistenceManager pm=null;  // do some JDO work  try {           pm = pmf.getPersistenceManager();             Iterator it = authors.iterator();             while (it.hasNext()) {                 Author author = (Author)it.next();                 authornames += author.getName() + " , " ; // build the string for next part                 pm.makePersistent(author);             }             pm.close();  // do some work on another EIS resouce  DataSource ds = (javax.sql.DataSource) ictx.lookup("AuthorXADataSource");            con = ds.getConnection();           ps = con.prepareStatement("update isbntable set authors = ? where isbn = ?");             ps.setString(1, authornames);             ps.setString(2, isbn);             ps.executeUpdate();             return true;         } catch (Exception e) {         System.out.println("An exception occured in the distributed tx " + e);             // mark the distributed Tx for rollback only  ctx.setRollbackOnly();  return false;         } finally {             try{             ps.close();             con.close();             pm.close();             }catch(Exception e){} // do nothing here         }     } } 

In summary, keep these guidelines in mind while working with the J2EE components and JDO:

  1. When using only JDO from Servlets and JSPs (i.e., local transactions), the JDO Transaction can be used to demarcate the transaction.

  2. When using JDO from Servlets and JSP alongside other transactional resources like JDBC and JMS, the UserTransaction must be used to demarcate the distributed transaction.

  3. When using only JDO in EJBs with bean-managed transactions to handle local transactions, use the UserTransaction and JDO automatically synchronizes itself.

  4. When using JDO with other transaction resources in bean-managed transactions, use the UserTransaction to demarcate the distributed transaction.

  5. When using container-managed transactions, do not use the JDO Transaction object in the EJB code.

  6. Application components that need to execute outside the J2EE container should use the javax.jdo.Transaction .

11.3.5 Transaction callbacks synchronization

We have looked at how the JDO Transaction object acts as a receiver of callbacks from the JTA transaction by having the vendor implementation of the interface register itself and implementing the javax.transaction.Synchronization interface. The Trasanction object can also act as a supplier of callbacks to developer-written listeners that are registered with it. For example, developers can write a class that implements the Synchronization interface and register it with the JDO Transaction object, as shown here:

 
 Transaction tx = pm.currentTransaction();     tx.setSynchronization(myobject); // myobject is an instance of a developer written class 

This allows the application to receive notifications on the lifecycle of the transaction in which they participate. When the JDO implementation invokes the afterCompletion(int status) and beforeCompletion() methods, the application can trap these events and perform necessary work or restore other states. This can be very useful in the J2EE environment in which the session state is being maintained in the HttpSession . For example, HttpSession instances do not have a mechanism that allows them to receive transaction notifications, so objects added to the HttpSession during a transaction are not removed if the transaction is rolled back. All changes to data in a session are durable despite the outcome of any executing transactions. Developers can use the Synchonization interface to perform such cleanup.



Core Java Data Objects
Core Java Data Objects
ISBN: 0131407317
EAN: 2147483647
Year: 2003
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net