11.3. Conversations with JPA

We now look at persistence context propagation and conversation implementation with JPA and EJB 3.0. Just as with native Hibernate, you must consider three points when you want to implement conversations with Java Persistence:

  • You want to propagate the persistence context so that one persistence context is used for all data access in a particular request. In Hibernate, this functionality is built in with the getCurrentSession() feature. JPA doesn't have this feature if it's deployed stand-alone in Java SE. On the other hand, thanks to the EJB 3.0 programming model and the well-defined scope and lifecycle of transactions and managed components, JPA in combination with EJBs is much more powerful than native Hibernate.

  • If you decide to use a detached objects approach as your conversation implementation strategy, you need to make changes to detached objects persistent. Hibernate offers reattachment and merging; JPA only supports merging. We discussed the differences in the previous chapter in detail, but we want to revisit it briefly with more realistic conversation examples.

  • If you decide to use the session-per-conversation approach as your conversation implementation strategy, you need to extend the persistence context to span a whole conversation. We look at the JPA persistence context scopes and explore how you can implement extended persistence contexts with JPA in Java SE and with EJB components.

Note that we again have to deal with JPA in two different environments: in plain Java SE and with EJBs in a Java EE environment. You may be more interested in one or the other when you read this section. We previously approached the subject of conversations with Hibernate by first talking about context propagation and then discussing long conversations. With JPA and EJB 3.0, we'll explore both at the same time, but in separate sections for Java SE and Java EE.

We first implement conversations with JPA in a Java SE application without any managed components or container. We're often going to refer to the differences between native Hibernate conversations, so make sure you understood the previous sections of this chapter. Let's discuss the three issues we identified earlier: persistence context propagation, merging of detached instances, and extended persistence contexts.

11.3.1. Persistence context propagation in Java SE

Consider again the controller from listing 11.1. This code relies on DAOs that execute the persistence operations. Here is again the implementation of such a data access object with Hibernate APIs:

public class ItemDAO {

    public Bid getMaxBid(Long itemId) {
        Session s = getSessionFactory().getCurrentSession();
        return (Bid) s.createQuery("...").uniqueResult();
    }
    ...

}

If you try to refactor this with JPA, your only choice seems to be this:

public class ItemDAO {

    public Bid getMaxBid(Long itemId) {
        Bid maxBid;
        EntityManager em = null;
        EntityTransaction tx = null;
        try {
            em  = getEntityManagerFactory().createEntityManager();
            tx = em.getTransaction();
            tx.begin();

            maxBid = (Bid) em.createQuery("...")
                              .getSingleResult();
            tx.commit();
        } finally {
            em.close();
        }
        return maxBid;
    }
    ...

}

No persistence-context propagation is defined in JPA, if the application handles the EntityManager on its own in Java SE. There is no equivalent to the getCurrentSession() method on the Hibernate SessionFactory.

The only way to get an EntityManager in Java SE is through instantiation with the createEntityManager() method on the factory. In other words, all your data access methods use their own EntityManager instance—this is the session-per-operation antipattern we identified earlier! Worse, there is no sensible location for transaction demarcation that spans several data access operations.

There are three possible solutions for this issue:

  • You can instantiate an EntityManager for the whole DAO when the DAO is created. This doesn't get you the persistence-context-per-request scope, but it's slightly better than one persistence context per operation. However, transaction demarcation is still an issue with this strategy; all DAO operations on all DAOs still can't be grouped as one atomic and isolated unit of work.

  • You can instantiate a single EntityManager in your controller and pass it into all DAOs when you create the DAOs (constructor injection). This solves the problem. The code that handles an EntityManager can be paired with transaction demarcation code in a single location, the controller.

  • You can instantiate a single EntityManager in an interceptor and bind it to a ThreadLocal variable in a helper class. The DAOs retrieve the current EntityManager from the ThreadLocal. This strategy simulates the getCurrentSession() functionality in Hibernate. The interceptor can also include transaction demarcation, and you can wrap the interceptor around your controller methods. Instead of writing this infrastructure yourself, consider EJBs first.

We leave it to you which strategy you prefer for persistence-context propagation in Java SE. Our recommendation is to consider Java EE components, EJBs, and the powerful context propagation that is then available to you. You can easily deploy a lightweight EJB container with your application, as you did in Chapter 2, section 2.2.3, "Introducing EJB components."

Let's move on to the second item on the list: the modification of detached instances in long conversations.

11.3.2. Merging detached objects in conversations

We already elaborated on the detached object concept and how you can reattach modified instances to a new persistence context or, alternatively, merge them into the new persistence context. Because JPA offers persistence operations only for merging, review the examples and notes about merging with native Hibernate code (in "Merging the state of a detached object" in Chapter 9, section 9.3.2.) and the discussion of detached objects in JPA, Chapter 9, section 9.4.2, "Working with detached entity instances."

Here we want to focus on a question we brought up earlier and look at it from a slightly different perspective. The question is, "Why is a persistent instance returned from the merge() operation?"

The long conversation you previously implemented with Hibernate has two steps, two events. In the first event, an auction item is retrieved for display. In the second event, the (probably modified) item is reattached to a new persistence context and the auction is closed.

Listing 11.6 shows the same controller, which can serve both events, with JPA and merging:

Listing 11-6. A controller that uses JPA to merge a detached object
public class ManageAuction {

    public Item getAuction(Long itemId) {
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        tx.begin();

        Item item = em.find(Item.class, itemId);

        tx.commit();
        em.close();

        return item;
    }

    public Item endAuction(Item item) {
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        tx.begin();

        // Merge item
        Item mergedItem = em.merge(item);

        // Set winning bid
        // Charge seller
        // Notify seller and winner
        // ... this code uses mergedItem!

        tx.commit();
        em.close();

        return mergedItem;
    }

}

There should be no code here that surprises you—you've seen all these operations many times. Consider the client that calls this controller, which is usually some kind of presentation code. First, the getAuction() method is called to retrieve an Item instance for display. Some time later, the second event is triggered, and the endAuction() method is called. The detached Item instance is passed into this method; however, the method also returns an Item instance. The returned Item, mergedItem, is a different instance! The client now has two Item objects: the old one and the new one.

As we pointed out in "Merging the state of a detached object" in section 9.3.2, the reference to the old instance should be considered obsolete by the client: It doesn't represent the latest state. Only the mergedItem is a reference to the up-to-date state. With merging instead of reattachment, it becomes the client's responsibility to discard obsolete references to stale objects. This usually isn't an issue, if you consider the following client code:

ManageAuction controller = new ManageAuction();

// First event
Item item = controller.getAuction( 1234l );

// Item is displayed on screen and modified...
item.setDescription("[SOLD] An item for sale");

// Second event
item = controller.endAuction(item);

The last line of code sets the merged result as the item variable value, so you effectively update this variable with a new reference. Keep in mind that this line updates only this variable. Any other code in the presentation layer that still has a reference to the old instance must also refresh variables—be careful. This effectively means that your presentation code has to be aware of the differences between reattachment and merge strategies.

We've observed that applications that have been constructed with an extended persistence context strategy are often easier to understand than applications that rely heavily on detached objects.

11.3.3. Extending the persistence context in Java SE

We already discussed the scope of a persistence context with JPA in Java SE in chapter 10, section 10.1.3, "Transactions with Java Persistence." Now we elaborate on these basics and focus on examples that show an extended persistence context with a conversation implementation.

The default persistence context scope

In JPA without EJBs, the persistence context is bound to the lifecycle and scope of an EntityManager instance. To reuse the same persistence context for all events in a conversation, you only have to reuse the same EntityManager to process all events.

An unsophisticated approach delegates this responsibility to the client of the conversation controller:

public static class ManageAuctionExtended {

    EntityManager em;

    public ManageAuctionExtended(EntityManager em) {
        this.em = em;
    }

    public Item getAuction(Long itemId) {
        EntityTransaction tx = em.getTransaction();

        tx.begin();

        Item item = em.find(Item.class, itemId);

        tx.commit();

        return item;
    }

    public Item endAuction(Item item) {
        EntityTransaction tx = em.getTransaction();

        tx.begin();

        // Merge item
        Item mergedItem = em.merge(item);

        // Set winning bid
        // Charge seller
        // Notify seller and winner
        // ... this code uses mergedItem!

        tx.commit();

        return mergedItem;
    }
}

The controller expects that the persistence context for the whole conversation is set in its constructor. The client now creates and closes the EntityManager:

// Begin persistence context and conversation
EntityManager em = emf.createEntityManager();

ManageAuctionExtended controller = new ManageAuctionExtended(em);

// First event
Item item = controller.getAuction( 1234l );

// Item is displayed on screen and modified...
item.setDescription("[SOLD] An item for sale");

// Second event
controller.endAuction(item);

// End persistence context and conversation
em.close();

Naturally, an interceptor that wraps the getAuction() and endAuction() methods and supplies the correct EntityManager instance can be more convenient. It also avoids the concern leaking upward to the presentation layer. You'd get this interceptor for free if you wrote your controller as a stateful EJB session bean.

When you try to apply this strategy with an extended persistence context that spans the whole conversation, you'll probably run into an issue that can break atomicity of the conversation—automatic flushing.

Preventing automatic flushing

Consider the following conversation, which adds an event as an intermediate step:

// Begin persistence context and conversation
EntityManager em = emf.createEntityManager();

ManageAuctionExtended controller = new ManageAuctionExtended(em);

// First event
Item item = controller.getAuction( 1234l );

// Item is displayed on screen and modified...
item.setDescription("[SOLD] An item for sale");

// Second event
if ( !controller.sellerHasEnoughMoney(seller) )
    throw new RuntimeException("Seller can't afford it!");

// Third event
controller.endAuction(item);

// End persistence context and conversation
em.close();

From looking at this new conversation client code, when do you think the updated item description is saved in the database? It depends on the flushing of the persistence context. You know that the default FlushMode in JPA is AUTO, which enables synchronization before a query is executed, and when a transaction is committed. The atomicity of the conversation depends on the implementation of the sellerHasEnoughMoney() method and whether it executes a query or commits a transaction.

Let's assume you wrap the operations that execute inside that method with a regular transaction block:

public class ManageAuctionExtended {
    ...

    public boolean sellerHasEnoughMoney(User seller) {
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        boolean sellerCanAffordIt = (Boolean)
            em.createQuery("select...").getSingleResult();

        tx.commit();

        return sellerCanAffordIt;
    }
    ...
}

The code snippet even includes two calls that trigger the flushing of the EntityManager's persistence context. First, FlushMode.AUTO means that the execution of the query triggers a flush. Second, the transaction commit triggers another flush. This obviously isn't what you want—you want to make the whole conversation atomic and prevent any flushing before the last event is completed.

Hibernate offers org.hibernate.FlushMode.MANUAL, which decouples transaction demarcation from the synchronization. Unfortunately, due to disagreements among the members of the JSR-220 expert group, javax.persis-tence.FlushMode only offers AUTO and COMMIT. Before we show you the "official" solution, here is how you can get FlushMode.MANUAL by falling back to a Hibernate API:

// Prepare Hibernate-specific EntityManager parameters
Map params = new HashMap();
params.put("org.hibernate.flushMode," "MANUAL");

// Begin persistence context with custom parameters
EntityManager em = emf.createEntityManager(params);

// Alternative: Fall back and disable automatic flushing
((org.hibernate.Session)em.getDelegate())
   .setFlushMode(org.hibernate.FlushMode.MANUAL);

// Begin conversation
ManageAuction controller = new ManageAuction(em);

// First event
Item item = controller.getAuction( 1234l );

// Item is displayed on screen and modified...
item.setDescription("[SOLD] An item for sale");

// Second event
if ( !controller.sellerHasEnoughMoney(seller) )
    throw new RuntimeException("Seller can't afford it!");

// Third event
controller.endAuction(item);

// End persistence context and conversation
em.close();

Don't forget that em.flush() must be called manually, in the last transaction in the third event—otherwise no modifications are made persistent:

public static class ManageAuctionExtended {
    ...
    public Item endAuction(Item item) {
        EntityTransaction tx = em.getTransaction();

        tx.begin();

        // Merge item
        ...
        // Set winning bid
        ...

        em.flush(); // Commit the conversation

        tx.commit();

        return mergedItem;
    }
}

The official architectural solution relies on nontransactional behavior. Instead of a simple FlushMode setting, you need to code your data-access operations without transaction boundaries. One of the reasons given by expert group members about the missing FlushMode is that "a transaction commit should make all modifications permanent." So, you can only disable flushing for the second step in the conversation by removing transaction demarcation:

public class ManageAuction {
    ...

    public boolean sellerHasEnoughMoney(User seller) {
        boolean sellerCanAffordIt = (Boolean)
            em.createQuery("select ...").getSingleResult();
        return sellerCanAffordIt;
    }
    ...
}

This code doesn't trigger a flush of the persistence context, because the EntityManager is used outside of any transaction boundaries. The EntityManager that executes this query is now working in autocommit mode, with all the interesting consequences we covered earlier in section 10.3, "Nontransactional data access." Even worse, you lose the ability to have repeatable reads: If the same query is executed twice, the two queries each execute on their own database connection in autocommit mode. They can return different results, so the database transaction isolation levels repeatable read and serializable have no effect. In other words, with the official solution, you can't get repeatable-read database transaction isolation and at the same time disable automatic flushing. The persistence-context cache can provide repeatable read only for entity queries, not for scalar queries.

We highly recommend that you consider Hibernate's FlushMode.MANUAL setting if you implement conversations with JPA. We also expect that this problem will be fixed in a future release of the specification; (almost) all JPA vendors already include a proprietary flush mode setting with the same effect as org.hibernate.FlushMode.MANUAL.

You now know how to write JPA conversations with detached entity instances and extended persistence contexts. We laid the foundation in the previous sections for the next step: the implementation of conversations with JPA and EJBs. If you now have the impression that JPA is more cumbersome than Hibernate, we think you may be surprised at how easy conversations are to implement once you introduce EJBs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.189.157