Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 86 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
86
Dung lượng
557,66 KB
Nội dung
492 CHAPTER 11 Implementing conversations Session session = getSessionFactory().openSession(); session.setFlushMode(FlushMode.MANUAL); // First step in the conversation session.beginTransaction(); Item item = (Item) session.get(Item.class, new Long(1)); session.getTransaction().commit(); // Second step in the conversation Item newItem = new Item(); session.persist(newItem); // Roll back the conversation! session.close(); The persist() method can delay inserts because it doesn’t have to return an identifier value. Note that the newItem entity is in persistent state after you call persist() , but it has no identifier value assigned if you map the persistent class with an identity or select generator strategy. The identifier value is assigned to the instance when the INSERT occurs, at flush-time. No SQL statement is executed when you call persist() outside of a transaction. The newItem object has only been queued for insertion. Keep in mind that the problem we’ve discussed depends on the selected iden- tifier generator strategy—you may not run into it, or you may be able to avoid it. The nontransactional behavior of persist() will be important again later in this chapter, when you write conversations with JPA and not Hibernate interfaces. Let’s first complete the implementation of a conversation with an extended Session . With a session-per-conversation strategy, you no longer have to detach and reattach (or merge) objects manually in your code. You must implement infrastructure code that can reuse the same Session for a whole conversation. Managing the current Session The current Session support we discussed earlier is a switchable mechanism. You’ve already seen two possible internal strategies: One was thread-bound, and the other bound the current Session to the JTA transaction. Both, however, closed the Session at the end of the transaction. You need a different scope of the Session for the session-per-conversation pattern, but you still want to be able to access the current Session in your application code. A third built-in option does exactly what you want for the session-per-conversa- tion strategy. You have to enable it by setting the hibernate.current_session_ context_class configuration option to managed . The other built-in options we’ve discussed are thread and jta , the latter being enabled implicitly if you configure Hibernate for JTA deployment. Note that all these built-in options are implemen- tations of the org.hibernate.context.CurrentSessionContext interface; you Conversations with Hibernate 493 could write your own implementation and name the class in the configuration. This usually isn’t necessary, because the built-in options cover most cases. The Hibernate built-in implementation you just enabled is called managed because it delegates the responsibility for managing the scope, the start and end of the current Session , to you. You manage the scope of the Session with three static methods: public class ManagedSessionContext implements CurrentSessionContext { public static Session bind(Session session) { } public static Session unbind(SessionFactory factory) { } public static boolean hasBind(SessionFactory factory) { } } You can probably already guess what the implementation of a session-per-conver- sation strategy has to do: ■ When a conversation starts, a new Session must be opened and bound with ManagedSessionContext.bind() to serve the first request in the conversa- tion. You also have to set FlushMode.MANUAL on that new Session , because you don’t want any persistence context synchronization to occur behind your back. ■ All data-access code that now calls sessionFactory.getCurrentSession() receives the Session you bound. ■ When a request in the conversation completes, you need to call Managed- SessionContext.unbind() and store the now disconnected Session some- where until the next request in the conversation is made. Or, if this was the last request in the conversation, you need to flush and close the Session . All these steps can be implemented in an interceptor. Creating a conversation interceptor You need an interceptor that is triggered automatically for each request event in a conversation. If you use EJBs, as you’ll do soon, you get much of this infrastructure code for free. If you write a non- Java EE application, you have to write your own interceptor. There are many ways how to do this; we show you an abstract inter- ceptor that only demonstrates the concept. You can find working and tested inter- ceptor implementations for web applications in the CaveatEmptor download in the org.hibernate.ce.auction.web.filter package. Let’s assume that the interceptor runs whenever an event in a conversation has to be processed. We also assume that each event must go through a front door controller and its execute() action method—the easiest scenario. You can now wrap an interceptor around this method; that is, you write an interceptor that is 494 CHAPTER 11 Implementing conversations called before and after this method executes. This is shown in figure 11.4; read the numbered items from left to right. When the first request in a conversation hits the server, the interceptor runs and opens a new Session B; automatic flushing of this Session is immediately disabled. This Session is then bound into Hibernate’s ManagedSessionContext . A transaction is started C before the interceptor lets the controller handle the event. All code that runs inside this controller (or any DAO called by the control- ler) can now call sessionFactory.getCurrentSession() and work with the Ses- sion . When the controller finishes its work, the interceptor runs again and unbinds the current Session D. After the transaction is committed E, the Ses- sion is disconnected automatically and can be stored during user think-time. Now the server waits for the second request in the conversation. As soon as the second request hits the server, the interceptor runs, detects that there is a disconnected stored Session , and binds it into the ManagedSession- Context F. The controller handles the event after a transaction was started by the interceptor G. When the controller finishes its work, the interceptor runs again and unbinds the current Session from Hibernate. However, instead of discon- necting and storing it, the interceptor now detects that this is the end of the conversation and that the Session needs to be flushed H, before the transaction is committed I. Finally, the conversation is complete and the interceptor closes the Session J. s.beginTransaction() s = sf.openSession() s.setFlushMode(MANUAL) MSC.bind(s) commit() s = MSC.unbind() 1. 2. 3. 4. s.beginTransaction() commit() 6. 8. MSC.bind(s) 5. 7. s = MSC.unbind() 9. s.close() s.flush() Figure 11.4 Interception of events to manage the lifecycle of a Session Conversations with Hibernate 495 This sounds more complex than it is in code. Listing 11.5 is a pseudoimple- mentation of such an interceptor: public class ConversationInterceptor { public Object invoke(Method method) { // Which Session to use? Session currentSession = null; if (disconnectedSession == null) { // Start of a new conversation currentSession = sessionFactory.openSession(); currentSession.setFlushMode(FlushMode.MANUAL); } else { // In the middle of a conversation currentSession = disconnectedSession; } // Bind before processing event ManagedSessionContext.bind(currentSession); // Begin a database transaction, reconnects Session currentSession.beginTransaction(); // Process the event by invoking the wrapped execute() Object returnValue = method.invoke(); // Unbind after processing the event currentSession = ManagedSessionContext.unbind(sessionFactory); // Decide if this was the last event in the conversation if ( returnValue.containsEndOfConversationToken() ) { // The event was the last event: flush, commit, close currentSession.flush(); currentSession.getTransaction().commit(); currentSession.close(); disconnectedSession = null; // Clean up } else { // Event was not the last event, continue conversation currentSession.getTransaction().commit(); // Disconnects disconnectedSession = currentSession; } return returnValue; } } Listing 11.5 An interceptor implements the session-per-conversation strategy 496 CHAPTER 11 Implementing conversations The invoke(Method) interceptor wraps around the execute() operation of the controller. This interception code runs every time a request from the application user has to be processed. When it returns, you check whether the return value contains a special token or marker. This token signals that this was the last event that has to be processed in a particular conversation. You now flush the Session , commit all changes, and close the Session . If this wasn’t the last event of the con- versation, you commit the database transaction, store the disconnected Session , and continue to wait for the next event in the conversation. This interceptor is transparent for any client code that calls execute() . It’s also transparent to any code that runs inside execute () : Any data access opera- tion uses the current Session ; concerns are separated properly. We don’t even have to show you the data-access code, because it’s free from any database transac- tion demarcation or Session handling. Just load and store objects with getCur- rentSession() . The following questions are probably on your mind: ■ Where is the disconnectedSession stored while the application waits for the user to send the next request in a conversation? It can be stored in the HttpSession or even in a stateful EJB. If you don’t use EJBs, this responsibility is delegated to your application code. If you use EJB 3.0 and JPA, you can bind the scope of the persistence context, the equivalent of a Session , to a stateful EJB— another advantage of the simplified programming model. ■ Where does the special token that marks the end of the conversation come from? In our abstract example, this token is present in the return value of the exe- cute() method. There are many ways to implement such a special signal to the interceptor, as long as you find a way to transport it there. Putting it in the result of the event processing is a pragmatic solution. This completes our discussion of persistence-context propagation and conversa- tion implementation with Hibernate. We shortened and simplified quite a few examples in the past sections to make it easier for you to understand the con- cepts. If you want to go ahead and implement more sophisticated units of work with Hibernate, we suggest that you first also read chapter 16. On the other hand, if you aren’t using Hibernate APIs but want to work with Java Persistence and EJB 3.0 components, read on. Conversations with JPA 497 11.3 Conversations with JPA We now look at persistence context propagation and conversation implementa- tion 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 con- text 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 conversa- tion 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 sub- ject 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 previ- ous sections of this chapter. Let’s discuss the three issues we identified earlier: per- sistence context propagation, merging of detached instances, and extended persistence contexts. 498 CHAPTER 11 Implementing conversations 11.3.1 Persistence context propagation in Java SE Consider again the controller from listing 11.1. This code relies on DAOs that exe- cute 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 getCur- rentSession() 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: Conversations with JPA 499 ■ 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, trans- action 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 getCur- rentSession() 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, con- sider 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 500 CHAPTER 11 Implementing conversations 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: 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 opera- tions 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 trig- gered, and the endAuction() method is called. The detached Item instance is passed into this method; however, the method also returns an Item instance. The Listing 11.6 A controller that uses JPA to merge a detached object Conversations with JPA 501 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 responsi- bility 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 effec- tively 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 effec- tively 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 pro- cess all events. An unsophisticated approach delegates this responsibility to the client of the conversation controller: [...]... propagation is available with thread or JTA transaction binding in Java SE and Java EE Persistence contexts are either scoped to the transaction, or managed by the application Java Persistence standardizes a persistence context propagation model for Java EE only, deeply integrated with EJB 3.0 components Persistence context scoping, to transactions or to stateful session beans, is well defined Hibernate supports... how FlushMode.MANUAL, a Hibernate feature, can disable flushing of your persistence context independently from your transaction assembly Table 11.2 shows a summary you can use to compare native Hibernate features and Java Persistence 516 CHAPTER 11 Implementing conversations Table 11.2 Hibernate and JPA comparison chart for chapter 11 Hibernate Core Java Persistence and EJB 3.0 Persistence context propagation... for transitive persistence The best known is persistence by reachability; we discuss it first Although some basic principles are the same, Hibernate uses its own, more powerful model, as you’ll see later The same Transitive persistence 519 is true for Java Persistence, which also has the concept of transitive persistence and almost all the options Hibernate natively provides 12.1.1 Persistence by reachability... modification replicate org .hibernate. annotations.CascadeType.REPLICATE Hibernate navigates the association and cascades the replicate() operation to associated objects evict org .hibernate. annotations.CascadeType.EVICT Hibernate evicts associated objects from the persistence context when an object is passed to evict() on the Hibernate Session refresh javax .persistence. CascadeType.REFRESH Hibernate rereads the... in Java SE and with EJBs On the other hand, persistence- context propagation and extended persistence- context management with JPA become much easier when you introduce EJBs and then rely on the standardized context propagation rules and the integration of JPA with the EJB 3.0 programming model Let’s first focus on the persistence- context propagation in EJB invocations 11.4.1 Context propagation with. .. detached instances persist javax .persistence. CascadeType.PERSIST Hibernate makes any associated transient instance persistent when an object is passed to persist() If you use native Hibernate, cascading occurs only at call-time If you use the EntityManager module, this operation is cascaded when the persistence context is flushed merge Javax .persistence. CascadeType.MERGE Hibernate navigates the association... between the Hibernate vendor extension with FlushMode.MANUAL and the official approach with nontransactional operations Disabling flushing with a Hibernate extension Let’s first write a stateful EJB, the conversation controller, with the easier Hibernate extension: @Stateful @TransactionAttribute(TransactionAttributeType.REQUIRED) public class ManageAuctionBean implements ManageAuction { @PersistenceContext(... conversation implementation with detached objects, these objects can be reattached or merged during a conversation Java Persistence standardizes merging of detached objects, but has no support for reattachment Hibernate supports disabling automatic flushing of persistence contexts for long conversations with the FlushMode.MANUAL option Disabling automatic flushing of an extended persistence context requires... most efficient processing options You should be familiar with the basic object states and the persistence interfaces; the previous chapters are required reading to understand this chapter First we’ll show you how transitive persistence can make your work with complex object networks easier The cascading options you can enable in Hibernate and Java Persistence applications significantly reduce the amount... In this chapter, you implemented conversations with Hibernate, JPA, and EJB 3.0 components You learned how to propagate the current Hibernate Session and the persistence context to create more complex layered applications without leaking concerns You’ve also seen that persistence- context propagation is a deeply integrated feature of EJB 3.0 and that a persistence context can be easily bound to the . Conversations with JPA 4 97 11.3 Conversations with JPA We now look at persistence context propagation and conversation implementa- tion with JPA and EJB 3.0. Just as with native Hibernate, you. 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. conversations with Java Persistence: ■ You want to propagate the persistence context so that one persistence con- text is used for all data access in a particular request. In Hibernate, this functionality