9.2 Session Beans and JDO


A typical session bean has the following characteristics, according to the EJB specification:

  • Executes on behalf of a single client.

  • Can be transaction-aware.

  • Often updates shared data in an underlying database.

  • Does not represent directly shared data in the database, although it may access and update such data.

  • Is relatively short-lived.

  • A typical EJB container provides a scalable runtime environment to execute a large number of session objects concurrently.

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:

Container-managed transactions ( CMT ) in which the EJB container coordinates transactions across business method calls. A bean using CMT declares in its XML deployment descriptor its transactional properties.

Bean-management transactions ( BMT ) in which a bean's implementation itself controls transactions via explicit code, i.e., begins and commits rollbacks transactions.

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:

  • Stateless session bean with container-managed transactions.

  • Stateful session bean with container-managed transactions.

  • Stateless session bean with bean-managed transactions.

  • Stateful session bean with bean-managed transactions.

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 management

Other 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 transaction

When 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 transaction

As 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]

[2] If optimistic JDO transactions and a transactional in-memory cache are used, as opposed to datastore transactions, then there may not actually be an underlying datastore transaction associated. Note that it is still possible to use JDO optimistic transactions even when using JTA user transactions, by specifying setOptimistic(true ) on the PersistenceManagerFactory instead of the javax.jdo.Transaction class , which would not be used in this scenario.

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 JTA

CMT 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:

  • Required : Automatically begins a new transaction if none, and in this case, commits before returning to the caller.

  • Mandatory : If existing, then as in Required , else throw Exception. Never automatically begin a transaction.

  • Never : Always to be called without a transactional context, if existing then throw Exception. Could potentially be used together with JDO NontransactionalRead in respective methods.

These configurations would generally be more exotic and less used:

  • NotSupported : "Unspecified transaction context." If existing, then suspend.

  • Supports : Like Required if existing; like NotSupported if none.

  • RequiresNew : If none, then begin new transaction and commit before return. If existing, then suspend.

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 example

Without 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.

How Did the JDO PMF Get into JNDI?

In the code above, the PersistenceManagerFactory is obtained via a lookup from JNDI, instead of using the JDOHelper as in the simpler examples elsewhere in this book. How, when and by whom did that PMF get bound into JNDI in the first place?

The idea in line with global J2EE architecture is that the JDO PMF is bound into JDNI by a JCA adaptor; more details on this can be found in Chapters 11 and 12.

Alternatively, a simple plain vanilla Java "start-up class" could achieve a similar goal, by manually creating a PersistenceManagerFactory , presumably using the JDOHelper class, and binding it into JNDI. However, J2EE "start-up" classes are a non-standard feature that depend on respective proprietary Application Server APIs. Furthermore, and more importantly, such a manual approach would defeat the purpose of the JCA architecture that JDO can leverage in a managed scenario, and would make it difficult to use container-managed (possibly distributed) transactions.


9.2.3 Stateful session bean with CMT example

As 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 serialization

Let'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 persistence-capable class is not declared serializable .

  • Persistent objects are accessed by the container for serialization before returning from method, but after transaction has been committed (both CMT and BMT).

  • Most importantly, the collection returned by Query.execute() is not serializable . Worse yet, it could be holding onto datastore resources such as cursors , and so on.

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:

  • You can use handcrafted (or code-generated) non-persistence-capable but serializable helper classes specifically for transporting the data between tiers. Readers familiar with other literature will recognize this as a familiar architecture choice, with Sun's J2EE patterns literature referring to this concept as value objects (VO), while other sources prefer the term of data transfer object (DTO) for the same idea. Instead of returning a collection of persistence capable Book instances, we would create instances of new transfer objects in the service. We restrain from the initial temptation of making this BookVO class extend the Book class. Similarly, we do not use a constructor that takes the persistent object. Instead of using a Constructor taking individual arguments, a dedicated Assembler class could have been used as well. This ensures decoupling and avoids dependency, so that only the Serializable class is required on the classpath of the EJB client, and the persistent Book class remains restricted to the server with access to the domain model.

     
     public class BookVO implements Serializable {   private String author;   private String isbn;    public BookVO(String bookAuthor, bookISBN) {       author = bookAuthor;       isbn = bookISBN;    }    public String getAuthor() { return author; }    public String getISBN() { return isbn; }    // Note: Delibarately no setters yet!  See below. } public Collection getBooks() {    PersistenceManager pm = null;    List results = null;    try {        pm = jdoPMF.getPersistenceManager();        Query q = pm.newQuery(Book.class);        // q.setFilter("...");        Collection jdoResult = (Collection)q.execute();        results = new ArrayList(jdoResult.size());        Iterator it = results.iterator();        while (it.hasNext()) {            Book pcBook = (Book)it.next();            BookVO dtoBook = new BookVO(pcBook.getAuthor(), pcBook.getISBN());            results.add(dtoBook);        }        q.close(jdoResult);    } catch (Exception ex) {        ejbCtx.setRollbackOnly();    // sic!        throw new EJBException(ex);    } finally {        try {            if (pm != null && !pm.isClosed())                pm.close();        } catch (Exception ex) {            // Log it        }    }    return results; } 
  • Alternatively, it is perfectly possible to actually tag the Book persistence capable class as serializable in its declaration. An interesting and useful feature of JDO in this respect is that persistence-capable classes are guaranteed to implement Java JDK Serialization in a manner such that enhanced classes can be serialized to and from non-enhanced classes; i.e., a correct serialVersionUID will be calculated by an enhancer , and so on. It is thus perfectly possible to use an enhanced and persistence-capable version of a class in the service implementation on a server, and the non-enhanced code of the same class on a client. Alternatively, the same enhanced class could also be used on the client; it will deserialize into an environment without JDO runtime, and be in transient state. Transient objects are guaranteed to behave like non-enhanced ones. An additional issue in this option as opposed to the one above is that the commit that the EJB container performs before the return (in CMT, or the demarcation the bean would do in BMT) has possibly flushed the persistent objects. So if the container tries to access objects for serialization after the commit, it gets an exception because there is no active transaction. This can be addressed by a makeTransient() call. Related to this, if there was any chance that not all attributes are read into memory yet, maybe due to a pre-fetch limitation, then invoking retrieve() before makeTransient() ensures that all persistent fields are loaded into the persistent object by the respective PersistenceManager . Here is an example code illustrating this:

     
     public class Book implements Serializable {    ... public Collection getBooks() {    PersistenceManager pm = null;    List results = null;    try {        pm = jdoPMF.getPersistenceManager();        Query q = pm.newQuery(Book.class);        // q.setFilter("...");        Collection jdoResult = (Collection)q.execute();        pm.retrieveAll(jdoResult);        results = new ArrayList(jdoResult);        pm.makeTransientAll(results);        // Or use loop to iterated and individually        // retrieve/makeTransient instead of xyzAll():        //        // results = new ArrayList(jdoResult.size());        // Iterator it = results.iterator();        // while (it.hasNext()) {        //    Book pcBook = (Book)it.next();        //    pm.retrieve(pcBook);        //    pm.makeTransient(pcBook);  // !        //    results.add(pcBook);        // }        q.close(jdoResult);    } catch (Exception ex) {        ejbCtx.setRollbackOnly();    // sic!        throw new EJBException(ex);    } finally {        try {            if (pm != null && !pm.isClosed())                pm.close();        } catch (Exception ex) {            // Log it        }    }    return results; } 

Possible issue: makeTransient() on dirty object

A specific corner case of the above described general pattern can cause an issue in some applications. A closer look at the State Transition table in the JDO specification reveals that invoking makeTransient() on a persistence-capable instance in persistent-dirty , persistent-new, persistent-deleted or persistent-new -deleted state is considered an error, and throws an exception.

In cleartext, if a persistent object is modified in the EJB business method, and the transaction is not committed at the time makeTransient() is called, then makeTransient() will fail.

We are calling this somewhat of a corner case because many real-world business methods (including the example above) may never modify persistent objects before they need to be returned to the client, thus made transient. Also, if the other approach is used, using data transfer objects (DTO), then this issue never occurs anyway because as we have seen above, the respective fields of the persistent object are copied into the DTO and the persistent object is never made transient. Furthermore, if bean-managed transactions (BMT) are used, then the correct ordering of operation (commit first to flush dirty objects, thus making dirty instances persistent-clean again, then makeTransient ) can help to work-around this.

Still, future JDO specifications may explicitly address this case. If you encounter this issue in your project, contact your JDO vendor about this, they may be able to offer a proprietary solution until the standard catches up with this.


These are arguments for choosing one or the other of above strategies:

  • Although coding additional classes for the first option may seem like a disadvantage of that approach at first, the implied reduction of coupling between the domain model used in the session bean implementation based on JDO, and the client, usually a presentation tier, can be an advantage in the longer term.

  • If a service returns a condensed summary view, an analysis, results of a calculation, or any other information not directly represented by an existing class of the domain model, a dedicated non-persistence-capable DTO class likely makes sense anyway.

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).

graphics/09fig03.jpg

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:

  • Code could explicitly nullify references after making the instance transient.

  • The domain model could be changed to make it "serialization-friendly" by taking advantage of JDO's ability to declare a field as transient for the purpose of Java serialization, yet persistent for the purpose of JDO. This is technically possible by specifying the Java transient keyword on the attribute, together with explicitly marking the field with persistence-modifier="persistent" in the JDO XML metadata descriptor.

  • We could change the domain model to make it "serialization-friendly" by attempting to "direct" associations in the "right" way, for the above example, making the relationship from Category to Book (as a Collection) instead of from Book to Category so that Book can be serialized.

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: Parameters

Another 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:

  • If the domain model uses JDO application identity and the key fields are part of the DTO, e.g., a persistent book class that would use the ISBN number as application identity and define a corresponding BookID class, the update code for the above example would look like this (issues around change of existing persistent object's application identity are ignored for simplicity):

     
     public void updateBook(BookVO2 updatedBook) {     PersistenceManager pm = null;     try {         BookID objId;         Book pcBook;         pm = jdoPMF.getPersistenceManager();         objId = new BookID(updatedBook.getISBN());         pcBook = (Book)pm.getObjectById(objId, true);         pcBook.setAuthor(updatedBook.getAuthor());     } catch (Exception ex) {         ejbCtx.setRollbackOnly();         throw new EJBException(ex);     } finally {         try {             if (pm != null && !pm.isClosed())                 pm.close();         } catch (Exception ex) {             // Log it         }     }  } 
  • If the domain model uses JDO datastore identity, the persistent Book's object identity needs to be specified either via an extra parameter to the update-type method, or included in the DTO. Either way, this object identity should be transformed into a String for traveling across tiers, because using a parameter or member variable of type Object would introduce a dependency on the respective JDO implementation's class for datastore identity. So an extended DTO could look like this:

     
     public class BookVO3 extends BookVO2 {     private String oid;     public BookVO3(String _oid) {         oid = _oid;     }     public String getOID()  { return oid; } } 

    Although it is tempting at first to obtain the persistence-capable object's ID from within the DTO constructor, this would create a dependency on JDO in the DTO, and thus on the client, and should be avoided. Instead, either use a separate assembler class or pass the OID as parameter to the constructor as shown above, obtaining it via PersistenceManager.getObjectId(pcBook).toString() when constructing the DTO. The PersistenceManager.newObjectIdInstance() method then proves helpful for converting such an OID string back into a JDO identity in the service:

     
     public void updateBook(BookVO3 updatedBook) {   PersistenceManager pm = null;   try {       Object objId;       Book pcBook;       pm = jdoPMF.getPersistenceManager();       objId = pm.newObjectIdInstance(Book.class,                              updatedBook.getOID());       pcBook = (Book)pm.getObjectById(objId, true);       pcBook.setAuthor(updatedBook.getAuthor());    } catch (Exception ex) {        ejbCtx.setRollbackOnly();        throw new EJBException(ex);    } finally {        try {            if (pm != null && !pm.isClosed())                pm.close();        } catch (Exception ex) {            // Log it        }    } } 
  • If the object to update is part of a larger DTO, then none of the above is needed, because we already have an object reference to the respective persistent object, without having to go through ID objects. Care must be taken, however, to repeat the basic "find persistent instance, and then update it" cycle for dependent DTO instances as well, in order to avoid assigning transient DTO objects to reference fields of persistent objects.

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.

Web Services

The latest EJB specification standardizes how stateless session beans may be exposed as Web services; this is termed a Web-service client view on a Web-service endpoint interface. So if you already have stateless session beans and an application server that supports the standard, this should purely become a matter of configuration at deployment.

However, to implement Web services, you don't necessarily need EJB 2.1 or EJBs at all. Several application-server-independent third-party tools exist to build Web services around "plain vanilla" Java classes.

In both scenarios, data-transfer objects returned from services (and similarly service input arguments) are marshaled into XML data format according to a WSDL document in order to be packaged up in a Web-service SOAP response.




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