A typical session bean has the following characteristics, according to the EJB specification:
The EJB specification defines stateless session beans, which do no keep state between business method invocations, as well as stateful session beans, which can keep conversational state between business method invocations. In short, a stateful session bean holds a client-specific state in the business tier . Additionally, such beans can rely on two types of transaction demarcation mechanisms:
The choice of stateless versus stateful and the transaction management schema are technically unrelated and orthogonal to each other, which leads to four possible scenarios:
The first blend (stateless session bean with container-managed transactions) is generally the most useful and widely used, because it combines managed transactions with the stateless architecture. The stateless architecture best allows EJB containers to efficiently implement remote request load-balancing and fail-over features by reusing and sharing beans. Together, these are two of the major advantages of using EJBs over "plain vanilla " Java classes to implement business logic. Subsequently, we first examine more details regarding transaction management, and then look at some concrete code specifically for the case of stateless session bean with container-managed transactions and stateful session bean with container-managed transactions. 9.2.1 Transaction managementOther chapters of this book (specifically Chapters 8 and 11) provide the background on the issue of managed versus unmanaged JDO environments with its different transaction mechanisms. As a brief reminder, the transaction demarcation mechanism in a non-managed JDO environment uses the javax.jdo.Transaction interface returned by the PersistenceManager.currentTransaction() method. In a managed environment, however, a JDO application relies on the standard J2EE javax.transaction.UserTransaction from the Java Transaction API (JTA). 9.2.1.1 Bean-managed transactions (BMT) with JDO transactionWhen building session beans using bean-managed transactions, both the JDO and the JTA transaction demarcation API can be used. Should the choice be to use the javax.jdo.Transaction , then the code within a session bean's business method would not really differ much from standalone simple JDO code seen elsewhere in this book. So in a business method in a session bean with BMT, you would simply write: PersistenceManager pm = pmf.getPersistenceManager(); // No J2EE JTA transaction can be active at this point! pm.currentTransaction().begin(); // Do something using JDO now... pm.currentTransaction().commit(); pm.close(); This architecture could make sense for simple BMT session beans that do not require the transaction context to be propagated when other methods are called from it. If an enterprise component does require transaction propagation, however, it should either use the JTA API for explicit demarcation, or use CMT for implicit declarative demarcation. Both approaches are illustrated in the following paragraphs. 9.2.1.2 Bean-managed transactions (BMT) with JTA transactionAs is shown in Chapters 8 and 11, a JDO implementation that claims JCA-compliance (sometimes referred to as " n-tier ready" or "enterprise integration enabled" or listed as supporting "JDO managed-environment" or "application server environment" in documentation) is able to perfectly blend into the J2EE and thus the EJB environment. In this approach, a BMT-based business method would use a javax.transaction.UserTransaction , obtained via the EJB SessionContext.getUserTransaction() method, for transaction demarcation, as would any non-JDO related session bean. Only one condition must be true for this to happen: a request to the PersistenceManager from the PersistenceManagerFactory after having initiated a JTA transaction (BMT-scenario), or alternatively ensuring that code executes within an elsewhere initiated currently active JTA transaction (CMT scenario; see below). Respective code from a BMT session bean business method would look something like this: sessionContext.getUserTransaction().begin(); PersistenceManager pm = pmf.getPersistenceManager(); // Do something using JDO now... pm.close(); sessionContext.getUserTransaction().commit(); This allows a JDO implementation to implement PersistenceManagerFactory.getPersistenceManager() such that it returns a PersistenceManager associated with the javax.transaction.Transaction of the executing thread. If datastore transactions are used by JDO, the underlying data-store connection is also enlisted in the transaction. [2]
This returned PM is a new one just created, set up with the appropriate synchronizations and enlisted in the respective connection, as in the above example in which a user transaction was just initiated by the bean. If, however, a user transaction was already active and the BMT bean business method did not explicitly demarcate a transaction beginning, then a JDO implementation would ensure that the PM is an existing one currently correctly enlisted in the transaction. This is of particular importance for CMT as well, as explained below. Each EJB business method should obtain its persistence manager from the persistence manager factory and close the persistence manager before returning, as shown in the above code snippet. This is not strictly necessary for stateful session beans under BMT, but the implied holding of a reference to a persistence manager and user transaction in an instance variable is a dubious practice. In fact, as we have just said, a client is guaranteed to obtain a PM enlisted in the same user transaction, if it is still active. Obtaining a persistence manager and closing it before returning in each EJB business method is thus perfectly safe and preferred practice, and it causes no issues for transactions that span multiple bean method invocations. 9.2.1.3 Container-managed transactions (CMT) via JTACMT enterprise beans can use neither the JDO transaction demarcation javax.jdo.Transaction nor the JTA user transaction management methods begin() , commit() or rollback() in their code. On the contrary, their business methods are guaranteed to be called within an active JTA transaction, if so declared. CMT relies on the container, other enterprise beans, or external plain Java classes for transaction initiation via the javax.transaction.UserTransaction interface. As discussed above, this guarantees that the JDO PersistenceManager obtained in a business method of a CMT enterprise bean is always correctly associated and enlisted in the respective J2EE transaction. When using CMT with declarative transaction management via the EJB deployment descriptor, the respective attributes in the XML descriptors must be understood and set correctly, as per the EJB specification. The intended configuration usually is one of the following:
These configurations would generally be more exotic and less used:
In summary, it can be said that transaction management with enterprise session beans that rely on JDO for persistence matters really does not have to be any different from how EJB transaction demarcation has always been. The remainder of this chapter focuses on examples of how to use JDO from CMT session beans in order to keep transaction management out of sample code. 9.2.2 Stateless session bean using CMT exampleWithout further ado, let's dive into practice and look at how a full stateless session bean with container-managed transaction would be coded. The class would be declared like this: public class ExampleCMTBeanWithJDO implements SessionBean { private javax.ejb.SessionContext ejbCtx; private PersistenceManagerFactory jdoPMF; private InitialContext jndiInitCtx; // No other instance variables in a stateless bean! private final static String jndiName = "java:comp/env/jdo/bookstorePMF"; In setSessionContext() , the JDO PersistenceManagerFactory is acquired from JDNI and assigned to an instance variable of the session bean: public void setSessionContext(SessionContext sessionCtx) throws EJBException { ejbCtx = sessionCtx; try { jndiInitialContext = new InitialContext(); Object o = jndiInitCtx.lookup(jndiName); jdoPMF = (PersistenceManagerFactory) PortableRemoteObject.narrow (o, PersistenceManagerFactory.class); } catch (NamingException ex) { throw new EJBException(ex); } } The other EJB service methods ” ejbCreate() , ejbRemove() , ejbActivate() , and ejbPassivate() ”usually stay empty unless code is required for other (non-JDO related) purposes in business methods later: public void ejbCreate() throws javax.ejb.CreateException { } public void ejbRemove() throws EJBException { } public void ejbActivate() throws EJBException { } public void ejbPassivate() throws EJBException { } Now in the business methods of the EJB, JDO can be easily used according to this generic code template (ignore return and argument types for now; more on that later): public void doSomething(int arg) { PersistenceManager pm; try { pm = jdoPMF.getPersistenceManager(); // // Do something using JDO now... // } catch (Exception ex) { ejbCtx.setRollbackOnly(); // sic! throw new EJBException(ex); } finally { try { if (pm != null && !pm.isClosed()) pm.close(); } catch (Exception ex) { // Log it } } // Maybe, return something; } The core of the business logic, what it actually does, goes in the middle of the above method, after the getPersistenceManager() . We look closer at the details of such code, and any related JDO specifics, just below.
9.2.3 Stateful session bean with CMT exampleAs mentioned in the introduction, the J2EE specification provides for another type of session bean, the stateful session bean, which automatically maintains its conversational state across multiple client-invoked methods. Many business processes have simple conversations that can be modeled as completely stateless services via stateless session beans as already seen. Alternatively, if the service appears stateful but is simple enough, passing state from the client to the session bean as argument to each business method is a possibility. However, some use-cases describe business processes that are inherently conversational and require multiple and possibly varying sequences of method calls to complete. Sometimes, it makes more sense to model this as a stateful session bean. This can be more efficient than reconstructing state inside the bean from arguments each time, or retrieved from any persistent datastore. This book is not the place to outline such a choice in more detail. However, we examine how to build such a stateful session bean with access to JDO, should this be a preference over a completely stateless architecture for a given scenario. As above, for the stateless session bean example, we focus on container-managed transactions only in this code. BMT would be equally possible and look as outlined earlier. The declarations at the beginning of the bean implementation class are largely similar to the stateless source code example above. The one important difference is that, because this is a stateful session bean, we are now permitted to keep instance variables across method calls ”for example: private String bookISBN; This instance variable could remember the customer ID across business method invocations. Remember that instance variables of stateful session beans need to be serializable, so although an instance variable of type String or other simple type is just fine, holding onto an arbitrary persistent object would be asking for trouble and deserves a closer look: private Category currentCategory; // bad! Instead, if a stateful session bean indeed needs to hold onto the same persistent object between business method calls, it is generally preferable to keep the JDO object identity instead of the persistent object itself in an instance variable, like this: private Object catID; // ok. // ... public void doOneThing(String name) { PersistenceManager pm; try { pm = jdoPMF.getPersistenceManager(); // ... Category catPC = ... // e.g. from Query result catID = pm.getObjectId(catPC); } // ... Omitting error handling etc.it's as above finally { pm.close(); } } public void doAnotherThing() { PersistenceManager pm; try { pm = jdoPMF.getPersistenceManager(); Category CatPC = pm.getObjectById(CatID, true); // ... } // ... Omitting error handling etc.it's as above finally { pm.close(); } } Just because the enterprise bean is stateful does not imply that it is a good practice to keep a reference to a JDO PersistenceManager in an instance variable. As in the example above for a stateless session bean, the business methods of stateful session beans should still obtain a PM at the beginning of every method and close it at the end. Transactional integrity can still be guaranteed and is unrelated to holding onto the same PM from different stateful entity bean method calls. 9.2.4 Service-oriented architecture (SOA)In the above examples, we looked only at the structure of session enterprise beans, and abstracted the specifics of a service's method contract and inner workings. We examine those more closely here. Let's first briefly think about what we mean by a service. A service in this context is something phenomenally simple: It has a signature, is called with arguments, and returns something. More importantly, however, a service in this context is usually a remote service, often referred to as a remote session "fa §ade" because it fronts local code. Arguments and return values are thus passed by value, in a serialized form, not by reference. Of course, stateless session beans are not simply dumb remote procedure call (in Java, i.e., RMI) endpoints; they include transaction context propagation, possible clustering, some security authentication mechanism, and so on. Still, the fundamental architecture is one of a remote invocation. A service thus exposes coarse-grained use-cases to the public. Sometimes, such "services" offered by session beans have relatively straightforward incoming arguments and return simple outgoing results ”for example, a createXYZ(String name) method with simple arguments such as Strings for a name, and similarly, methods of type double getXYZ(String code) or something along those lines. Business methods with such signatures are easy to implement and should not require further discussion after what was presented so far. However, some session beans, more often than not, want to return (or receive as arguments in an updateXYZ() -style scenario) more complex types ”for example, a Book , an Author or other objects "inspired" by the persistence-capable objects from the actual underlying domain model. It is this second case that needs to be examined more closely in the context of JDO. The point here is that in a service-oriented architecture (SOA), a client cannot simply "navigate" to other objects of a domain model that were not explicitly returned by value. If a service does not return the data that a service invoker requires, then the service definition, the service implementation, and the service-invoking client need to be changed. In such a pure SOA architecture, clients of a session bean service (be it the Web tier or standalone EJB clients) also cannot use the JDO API directly, because this would be a violation of the service-oriented architecture. Clients would invoke only well-defined services. The objects returned from a service would thus not have a JDO environment on the client. This immediately leads to the question: What instances of which class should a service return and receive as arguments? We look at this in more detail in the following two sections. 9.2.4.1 Data-transfer objects (aka value objects) and JDO PC serializationLet's first examine the case of returning objects from an enterprise session bean service business method. We look at the reverse case, passing objects as arguments to a service, in the next section. For the sake of an illustrative example, imagine a library application: Suppose that the session bean outlined above had a method to return a collection of books; it could be objects that meet a certain query condition, such as "has copies that the currently authenticated user has borrowed," or it could be a given number of objects, starting from a given index, or anything else that requires the method to return a collection of persistent objects. For the initial discussion, let's assume that the persistent Book class does not have any Java references to other persistent objects and looks something like this: public class Book { // // JDO persistent-capable class that will be enhanced! // private String author; private String isbn; public String getAuthor() { return author; } public void setAuthor(String _a) { author = _a; } public String getISBN() { return isbn; } public void setISBN(String _isbn) { isnb = _isbn; } } This is often overly simplistic for most real-world applications, but bear with it as a first example for good reasons. We look into the matter of graphs of objects later in this chapter. A developer may be tempted to write at first, without further thought, something like this (this code is intentionally wrong, so be sure to see the discussion below): public Collection getBooks(/* e.g. int start, int count */) { PersistenceManager pm = null; // try/catch/finally error handling omitted, as above pm = jdoPMF.getPersistenceManager(); Query q = pm.newQuery(Book.class); // q.setFilter("..."); Collection results = (Collection)q.execute(); pm.close(); return results; // BAD. WRONG. See below. } This will not work, for several reasons:
The last point can be easily addressed, so let's get this out of the way first. The following code addresses that issue by simply copying JDO query results into a JDK standard Collection , here an ArrayList . It is assumed that a filter expression has limited the size of the result collection to a " sensible " size: public Collection getBooks(/* int start, int count* /) { PersistenceManager pm = null; List results = null; // try/catch/finally error handling omitted, as above pm = jdoPMF.getPersistenceManager(); Query q = pm.newQuery(Book.class); // q.setFilter("..."); Collection jdoResult = (Collection)q.execute(); results = new ArrayList(jdoResult); q.close(jdoResult); pm.close(); return results; } This still won't work, though. As mentioned, the persistent objects are not serializable at this point. Because the Book instances contained in the ArrayList will "travel over the wire" to the remote client after the EJB business method returns, we have to stick something serializable into the collection. There are two ways to achieve this:
These are arguments for choosing one or the other of above strategies:
If a business method does not return a collection of objects, but merely one specific persistent object, the same issues would arise and the above points would equally apply. Let's now extend the domain model of our Book example slightly, but significantly. Let's assume that a small enhancement requested is that Books are categorized hierarchically. We introduce a new Category class, as indicated in Figure 9-3. Figure 9-3. Extended example model, Book with association (UML representation).
A Category is just an example. What matters is that the persistent class Book now has a relationship to another persistent class. You can easily imagine other cases; indeed, most real-world domain models have various associations between persistent classes. Persistent objects thus are nodes in a graph, with links to other persistent objects. And that's where it starts to get really interesting: With the approach of direct serialization of formerly persistent objects, which worked fine for the simple case above, we would now potentially serialize a huge data stream: The entire closure of instances would normally be serialized along. Referenced instances would also have to be made transient; retrieve() and makeTransient() would have to be manually invoked for the entire graph of reachable instances, because that is what serialization does. The makeTransient() call by itself is not propagated across the graph, although it would be possible to write graph traversal helper functions using reflection and semi-automating such procedures. However, the real question is this: Do we actually want the entire Category hierarchy (examine how the Category instances themselves hold references to a parent and subcategories ) to be serialized and sent over the wire with each Book object? Presumably not, and the same would apply to other similarly structured real-world domain models. It could seem that there are workarounds for some of these issues, for example:
However, all such approaches could rapidly defeat the purpose of an object-oriented transparent domain model. Using explicit data transfer objects and assemblers are usually a better choice for a true service-oriented architecture, albeit requiring extra coding. Attributes of more complex data transfer objects should always be simple types such as String , double , and so on, or other data transfer objects, all created by an Assembler. In the case of the above example, we could model a data transfer object (name it value object if you are more used to that terminology) for a persistent Book class that deliberately omits the link to Category , and instead contains less data, according specifically to the use-case that requires this service. For example: public class Category { // // JDO persistent-capable class that will be enhanced! // private Category parent; private List subCategories; private String name; // ... } public class Book { // // JDO persistent-capable class that will be enhanced! // private String author; private String isbn; private Category category; // ... } public class BookVO1 implements Serializable { private String author; private String isbn; private String category; // String, not Category! public BookVO1(String bookAuthor, String bookISBN, String bookCategoryName) { author = bookAuthor; isbn = bookISBN; category = bookCategoryName; // ! } public String getAuthor() { return author; } public String getISBN() { return isbn; } public String getCategory() { return category; } // Note: Deliberately no setters yet! See below. } // ... public Collection getBooks() { // Business logic method would not use retrieve() etc. // { BookVO dtoBook = new BookVO1(pcBook.getAuthor(), pcBook.getISBN(), pcBook.getCategory().getName()); In summary, the basic model is the same as has been recognized as an EJB best practice for some time already, with explicit data transfer or value objects returned from inside session beans. 9.2.4.2 Return-trip ticket: ParametersAnother issue worth mentioning is what we may call the "return trip" of value objects from a client tier: Most real-world applications offer remote clients business methods related to updating data. In their simplest form, such methods could look like this: void updateBook(String bookISBNKey, String newAuthor); However, given that we have already defined a data transfer class for Books above, it could make sense to allow a client to change instances of these on the client (introducing setter methods) and send them back to a service: public class BookVO2 implements Serializable { private String author; private String isbn; public String getAuthor() { return author; } void setAuthor(String _auth) { author = _auth; } public String getISBN() { return isbn; } void setISBN(String _isbn) { isbn = _isbn; } } public void updateBook(BookVO2 updatedBook) { // 1) Find persistent book // 2) Update persistent instance } Either way, the basic pattern is the same: First, find the persistent object, and then change its attributes either to values passed as arguments to the business method or from the DTO received as arguments. What differs is how to find the persistent object. Let's remind ourselves that we really do want to find an existing object and change it. From update-type methods, we never want to do something like this: Book pcBook = new Book(isbnCode); // wrong here! pm.makePersistent(pcBook); // wrong here! pcBook.setAuthor(newAuthor); The above code snippet would invariably lead to the creation of a new persistent instance, not an update of existing information. In order to find a persistent object to change, we need to decide how the identity of the object to be changed is known to the service implementation. Several options exist:
Various enhancements are possible around the approaches shown here. For example, optimizations to only update "relevant" persistent fields, usually a mechanism to recognize those that have actually been changed in data transfer objects. The basic pattern always stays the same, however.
|