HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

Java Data Object (JDO) is a standard API generically used to store, retrieve, and query user-written object classes to and from a data store.

What makes JDO stand out among other persistence options is that it's easy to use and flexible.

JDO provides transparent persistence so it's easy for developers to persist objects without doing any extra work. The tedious and routine persistence work is offloaded to the JDO vendor, leaving developers with time to concentrate on the business logic. In addition, JDO is flexible because it can work on any data store. While JDBC provides persistence only for relational databases, JDO is more generic, providing persistence for any data store, for example, relational databases, files, XML and object databases, etc., making applications very portable.

Overview
Metadata and Enhancer
In JDO, any object class to be persisted needs to be PersistenceCapable, while any class that references a persistence-capable object needs to be PersistenceAware. The good news is that with JDO transparent persistence, you don't have to program your class to implement PersistenceCapable or PersistenceAware. Just write your object class as usual, and the JDO vendor implementation will provide an Enhancer that will make your object class PersistenceCapable based on the metadata you provide. The only extra work you need to do is to write a metadata file in XML for your object. Listing 1 shows the metadata that I'll be using later in my code example.

The metadata is usually short and not hard to write because by default, JDO already derives a lot of information from the object class. Specify information in the metadata only when:

  • You need to override JDO default behavior, e.g., making a field nonpersistent even though it's not transient.
  • There's information JDO can't derive from the class definition, e.g., which field is the primary key or what kind of object is inside a Collection.

    PersistenceManager
    With object classes enhanced, you can then persist the object by using a PersistenceManager. To get a PersistenceManager, first specify a Property object, which usually contains:

    • Data store connectivity information
    • JDO vendor class name
    • Default settings for the PersistenceManager
    Lines 2­3 in Listing 3 show how you can get a PersistenceManager from a JDOPersistenceManagerFactory by specifying a Property object. Once you have a PersistenceManager, you can use it to add, update, delete, and query objects (which I'll discuss in the next section). When you're done, close the PersistenceManager to free up its resources at the end.

    Listing 3 shows code fragments of how you can persist and query objects using JDO. With a PersistenceManager pm, you can add a new object to a data store by makePersistent (Lines 6­8). An object only needs to be made persistent the first time it's known; once an object is already makePersistent, you can update the object directly by referencing its fields. All changes to that object will be saved to persistence storage when the transaction commits. If you don't like those changes, roll back to undo them (Lines 15­17). Similarly, you can delete an object with deletePersistent (Line 26).

    To access objects in the data store, iterate through an Extent, which is the logical representation of all persistent instances of a given persistence-capable class (Lines 12­15).

    However, if you want to be more selective and get only a subset of instances of a given class, create a query. To do this, pass a Candidate object and a Filter to the method newQuery. The Candidate object is a set of objects to select an instance from; it can be a collection of objects or an extent. The filter is a string written in JDO Query Language (JDOQL). Once you create the query, execute it and get back a collection of instances that match (Lines 22­26). JDOQL is the query language for JDO; it's somewhat similar to SQL but has a Java syntax. The example here is a simple one; with JDOQL, your Filter string can be much more sophisticated. In addition, if you declare parameters to act as placeholders in the filter string, you can write a single query and then execute it multiple times, supplying new values each time. There's a lot more to JDOQL that you can read up on; please refer to the links in the Resources section.

    A unique thing about JDO's query capability is that once you get an object, you can navigate to any other object referenced by that object. Frequently, all you need is to get a starting point object, and you can already get to any object related to it without having to run another query.

    An Object Persistence Example
    To find out if JDO is as good as promised, I'll write some code using JDO and JDBC to persist a Book object that I created (see Listing 4). This Book object contains a name and a Block object. To make things interesting, a Book has a constraint that each book is uniquely defined by its name, meaning that you're not allowed to add two books with the same name.

    A Block is a building block for a Book. It can be of type Document, Chapter, or Section. The root Block is of type Document and can contain any number of Chapter Blocks. Each Chapter Block can also contain any number of Section Blocks, so there's a recursive relationship here for Blocks. Within each Block, there's a HashMap that may store any number of attribute value pairs for that Block.

    Listing 5 shows a test Book I created for my example. This Book contains two chapters: Chapter 1 has one section and Chapter 2, two. In particular, Chapter 2 has an attribute Color=Red.

    Using this Book class, I want to implement some common persistence features as follows:

  • Add: See if I can successfully add two books to a data store, and that when I add a third book with the same name, an exception is thrown because the integrity constraint is violated for the unique book name.
  • Update: See if I can update a Book by adding an attribute "Comment" to its root Block. When I commit, the change should be saved, and if I roll back, the change should be discarded.
  • Delete: See if I can look up a book by a query and delete it from the data store.

    Without JDO, my usual way to implement this would be to design relational database tables to store all the data contained in a Book, then use SQL and JDBC to store/retrieve that data to/from the tables. Due to space constraints, I won't show you my JDBC implementation here, but you can download it from below if you're interested. Note that to implement the above features in JDBC/SQL, I have to write quite a lot of code (480 lines!). What I'm going to show you now is a much shorter way to address the same problem using JDO.

    Persisting a Book Object Using JDO
    To persist a Book object in JDO, although I'm using exactly the same object class as I would use if I implemented this in JDBC, the ID field in the Block object is now unnecessary. If I implement this in JDBC, the ID field would be needed to internally reference a different Block from the database table. However, using JDO, I don't have to worry about populating this field since JDO will handle that internally.

    To persist my Book object, I create metadata for both a Book and a Block (see Listing 1). In the metadata for Block, I specify that the object in the Collection children is of type Block (Lines 10­11), while the key and value for HashMap attributes are of type String (Lines 12­14). Also, since the ID field is not really needed for the Block object, I specify in the metadata that there's no need to persist it (Line 8). In the metadata for Book, I specify that nm is the primary key of Book (Line 5), and that the Book object should use my user-defined Application Identity BookKey as the object identity class (Line 4). The code for the class BookKey can be found in Listing 6.

    In this example, the JDO Vendor implementation I use is Kodo JDO (which uses a relational database). There are many JDO implementations in the market; you can choose to use any of them, and your application code doesn't need to be changed. For the data store, I use Enhydra InstantDB (a relational database included with the Kodo distribution). The essence of JDO is that the developer does not need to know how the vendors persist data to a database, so I don't need to design any table here even though we are using a relational database behind the scenes. The vendor Kodo provides a tool called schematool to help me create those tables based on my metadata. All I need to do now is run the following to prepare the database for my object.

    schematool ­action refresh Book
    schematool ­action refresh Block

    Next I compile my object classes as usual, then using Kodo's enhancer tool jdoc, I enhance my class file by running:

    jdoc.Book
    jdoc.Block
    jdoc.BookPersistJDO

    Here, as long as I put the unenhanced class files (Book.class, BlockPersist.class, BookPersistJDO.class) and the metadata file in a location where jdoc can find it, jdoc will then modify the bytecode of these classes to add methods necessary to make them PersistenceCapable or PersistenceAware. Classes with metadata will be enhanced to PersistenceCapable, while classes without metadata will be enhanced to PersistenceAware. In this example, Book.class and Block.class are enhanced to be PersistenceCapable while BookPersistJDO.class is enhanced to be PersistenceAware.

    Once I've enhanced my code, any persistence work for my object can be done via the PersistenceManager. Using the code I showed you previously, I can easily get a PersistenceManager pm and then add, delete, or update a book using it. Listing 7 shows fragments of my code BookPersistJDO.java. The method addBook (Line 3) shows how I add a Book in JDO, and the method deleteBook (Line 13) shows how I delete a book.

    To get a Book with a given name, I create a query using a Filter written with JDOQL. Executing the query, I get back a Collection of Book object that matches this query (Lines 25­29). Once I get a Book object, I directly update its field and then commit the changes.

    Figure 3 shows the results of running my test on this implementation. The results are as I expected. First, I successfully added books to the data store, and when I add a book with the same name, it gives me a JDOUserException, showing that the unique book name integrity is violated. Next, I'm able to update a book, then keep the changes by committing, or discard the changes by rolling back. Last, I'm able to query the data store to get a book by its name, and then delete the book from the data store.

    Figure 3
    Figure 3

    Comparison of Implementations
    By using JDO and JDBC to tackle the same problem of persisting a Book object, I observed the following:
    1.   Using JDO, I'm able to achieve the same things I can achieve with JDBC. I can query objects using JDOQL; maintain data integrity by specifying the nm of a Book as a unique primary key; and add, delete, and update my object.
    2.   JDO makes my transaction handling easier. In my JDBC implementation, because one Book object actually translates to many rows in four different relational tables, I have to ensure that all inserts or deletes to tables are done in a transaction. On the contrary, JDO saves or deletes the whole object to the database in one operation, and I don't need to use transactions to maintain the atomicity of the action.
    3.   The fact that BookPersistJDO.java (140 lines) is so much shorter than BookPersistJDBC.java (480 lines) shows that JDO simplifies my code a lot. This is especially so given the recursive nature of my object and how complicated it is to represent it in relational tables. In my JDBC implementation, I have to put a lot of thought into the design of my tables so I can store and retrieve the recursive data. I have to generate an ID for each Block to use as a link for the child/parent relationship. In the JDO implementation, I don't have to think about any of that, and everything is already persisted correctly.
    4.   The effort I put into maintaining a cache for performance in my JDBC implementation is not needed in JDO, because the JDO vendors are the ones who would implement data caching for performance improvement. That saves me a lot of work because I don't have to worry about keeping my cache in sync with the database all the time.

    Behind the scenes, the JDO vendor's implementation and my JDBC implementation are probably not much different. For example, this JDO vendor, using a relational database, may also have implemented it with a similar table design and ID generation scheme, and used JDBC to persist the data. However, the important point is I don't have to know about these implementation details: they're all offloaded to the JDO vendor who has expertise in that area, and who would likely implement it better. In addition, the vendor is also free to implement it with any other kind of data store like object databases and files, giving us more flexibility and choices on where to store the data.

    Conclusion
    JDO offers a lot of advantages for developers:

  • It has all the basic functionalities needed for data persistence: add, delete, update, transaction, data integrity, and data caching.
  • It takes over a lot of tedious work from the developer, making code easy and maintainable.
  • It's vendor independent, preventing vendor lock in.
  • Although my example doesn't show it, it can work on any data store, making development flexible and portable.

    JDO is a technology that's worth exploring. This article is a starting point for you; for more information see the resources section.

    Resources

  • The One-Stop Site for JDO: www.jdocentral.com
  • The Specification: http://access1.sun.com/jdo/
  • Roos, R. (2002). Java Data Objects. Addison Wesley: www.OgilviePartners.com
  • The JDO Vendor I used in my example: SolarMetric: www.solarmetric.com/Software/Kodo_JDO

    Properties for JDO
    Listing 2 provides the Property file that I'll use later. I'm using a relational data store and Lines 1­4 contain JDBC connectivity information. Line 6 contains the name of the JDO vendor class I'm using. Lines 8­10 are the default settings I want for the PersistenceManager. It specifies that I'm using optimistic transaction, and that values in the cache should be restored on a transaction rollback, and should not be retained on a commit. There are many more details on how you can use different options for transactions; read the JDO API to find out what they all mean and when to use them.

    JDBC Implementation
    Here I'll briefly describe how I implement the persistence of Book using JDBC. By knowing how much work is involved in this, you can better appreciate how much JDO does for you.

    To persist Book to a relational database, I created four tables (see Figure 1). Based on data integrity, the nm field in the Book table is defined as a unique key. To trace the relation between each Block to its children and attributes, I assign a blockId to each Block. This blockId is generated (by incrementing 1 to the largest blockId in the Block table) every time a new Block is added.

    Figure 1
    Figure 1

    Adding a Book object involves denormalizing the information within a Book object into four tables: Book, Block, BlockRelation, and BlockAttr. In particular, a blockId has to be generated for each Block. Furthermore, the tedious part is that Blocks are recursive, and I have to write code that recursively inserts the Block data.

    For the example Book created in Listing 5, the tables will be populated as shown in Figure 2. As you can see , one Book is translated into many rows in four tables, so all inserts have to be wrapped in a transaction so that a Book is added all or none, never partially. Similarly, deleting a Book involves finding the appropriate rows to delete in each of the four tables, while wrapping all deletes in a transaction so that a Book is deleted all or none.

    Figure 2
    Figure 2

    For better performance, I don't want to query four tables to recursively generate the Book object every time someone asks for a Book. Instead I created a BookCache that I populated at the beginning by generating all Book and Block objects from the four tables. As I add, delete, and update Books in the database, my code ensures that the BookCache is kept in sync. As a result of all this work done in maintaining a cache, to get a Book is as easy as looking up the cache by its name.

    Not Yet Perfect
    Despite all its advantages, there are still areas where JDO may not be perfect yet:

  • It's good for new development, but to convert existing schema in a relational database to use JDO requires a bit of mapping work.
  • As developers, we no longer deal with lower-level database access when using JDO, so it may be hard for us to tune performance. Since JDO implementation has to do a lot of extra work tracking the fields that are changed or synchronizing cache internally, etc., how well the JDO vendor implements it will be critical to performance.
  • JDOQL does not have an aggregate function such as max, min, and sum like SQL does.
  • It would be nice if there were better checking to catch bad JDOQL at compile time. For example, in a Filter, when you specify the field name in the class via a string, you can easily have specified a bad field name resulting in a JDOQL that will compile, but fail at execute time.
  • Some people thought that JDO's advantage is that you don't have to write SQL anymore; the truth is now you have to learn to write JDOQL instead!

    Author Bio
    Teresa Lau has been an independent Java consultant for over five years, with an emphasis on financial systems. She holds an MS in computer science and currently works in New York. [email protected]

    "Java Data Object"
    Vol. 8, Issue 3, p. 36

    	
    
    
    Listing 1   Metadata for my example
    
     1 <?xml version="1.0"?>
     2 <jdo>
     3   <package name="whs.jdo">
     4    <class name="Book" 
            identity-type="application"
            objectid-class="BookKey">
     5       <field name="nm" 
              primary-key="true" />
     6    </class>
     7   
     8    <class name="Block" >
     9      <field name="id"  
             persistence-modifier="none" />          
    10      <field name="children">
    11         <collection 
                element-type="Block"/>
    12      </field>
    13      <field name="attributes">
    14        <map key-type="String"/>
    15        <map value-type="String"/>
    16      </field>                       
    17    </class>
    18   </package>
    19 </jdo>
    
    Listing 2  Properties for setting up JDO
    
    1  javax.jdo.option.ConnectionUserName=
          Database user
    2  javax.jdo.option.ConnectionPassword=
          Password of the user
    3  javax.jdo.option.ConnectionURL=
          URL of the database
    4  javax.jdo.option.ConnectionDriverName=
         Classname of JDBC driver 
    5
    6  javax.jdo.PersistenceManagerFactoryClass=
          com.solarmetric.kodo.impl.jdbc.
          JDBCPersistenceManagerFactory
    7
    8  javax.jdo.option.Optimistic=true
    9  javax.jdo.option.RestoreValues=true
    10 javax.jdo.option.RetainValues=false
    
    Listing 3   JDO Code Fragment
    
    1  //  --- Get persistence manager ---
    2  PersistenceManagerFactory  factory = 
       JDOHelper.getPersistenceManagerFactory
         (property);
    3  PersistenceManager pm = 
          factory.getPersistenceManager()
    4
    5  // --- Add ---
    6  pm.currentTransaction().begin ();
    7  pm.makePersistent (obj);
    8  pm.currentTransaction().commit ();
    9
    10 // --- Iterate Extent & Update ---
    11 pm.currentTransaction().begin ();
    12 Extent ext =  pm.getExtent 
          (MyClass.class, false);
    13 for (Iterator i = ext.iterator ();  
           i.hasNext();) {
    14   MyClass obj = (MyClass)i.next ();
    15   obj.setField1("AA");
    16 }
    17 pm.currentTransaction().commit ();
    18
    19
    20  // --- Query and Delete ---
    21  pm.currentTransaction().begin ();
    22  String filter ="nm=\"JDO Book\"";
    23  Query qry= pm.newQuery(ext, filter);	
    24  Collection c =
              (Collection) qry.execute();
    25  Object obj = c.iterator().next();
    26  pm.deletePersistent (obj);
    27  pm.currentTransaction().commit();
    28
    39  // --- Close resources ---
    30  pm.close();
    
    Listing 4 Book & Block object
    
    1  class Book {
    2    String nm;
    3    Block block= new Block("Document");
    4
    5    Book(String name){
    6        this.nm = name;
    7    }
    8
    9    void addChild(Block e)  {
    10      block.addChild(e);
    11   }
    12  }
    13
    14  class Block {
    15    String type;
    16    Integer id;
    17    Map  attributes = new HashMap();
    18    List children= new ArrayList();		
    19	
    20    Block(String type)   {
    21       this.type = type;
    22    }
    23   
    24    void addChild(Block e) {
    25      children.add(e);
    26    }
    27  
    28    void setAttribute(String key, 
                            String attr){
    29        attributes.put(key, attr);
    30   }
    31 }
    
    Listing 5  Create Test Book
    
    1  Book book = new Book("Intro to JDO");
    2  Block chp1= new Block
    	("Overview", "Chapter");
    3  Block sec11 = new Block
    	("Advantage of JDO", "Section");
    4  chp1.addChild(sec11);
    5  Block chp2 = new Block
           	("Example", "Chapter");
    6  chp2.setAttribute("Color", "Red");
    7  Block sec21 =new Block
           	("JDBC Code", "Section");
    8  Block sec22 = new Block
           	("JDO Code", "Section");
    9  chp2.addChild(sec21);
    10 chp2.addChild(sec22);
    11 book.addChild(chp1);
    12 book.addChild(chp2);
    
    Listing 6 BookKey.java
    
    1  public final class BookKey  {
    2    public String nm = null;
    3    public BookKey(){}
    4
    5    public BookKey(String nm) {
    6       this.nm = nm;
    7    }
    8
    9    public boolean equals (Object o){
    10     if (o == this)   return true;
    11     if (!(o instanceof BookKey))  
               return false;
    12     return((BookKey)o).nm.equals(nm);
    13   }
    14
    15   public int hashCode () {
    16    return nm.hashCode();
    17   }
    18
    19   public String toString() {
    20    return nm;
    21   }
    22 }
    
    Listing 7 BookPersistJDO.java
    
    1  class BookPersistJDO {
    2
    3   public Book addBook (Book book)  
    4   throws JDOUserException {
    5     PersistenceManager pm =getPM(); 
    6     Transaction tran = 
              pm.currentTransaction ();
    7     tran.begin ();
    8     pm.makePersistent (book);
    9     tran.commit ();
    10    return book;
    11  }
    12 
    13  public void deleteBook (Book book){
    14    PersistenceManager pm =getPM();
    15    Transaction tran =  
            pm.currentTransaction();
    16    tran.begin ();
    17    pm.deletePersistent (book);
    18    tran.commit ();
    19  }
    14
    15  public Collection  getAllBooks() {
    15    PersistenceManager pm = getPM ();				
    17    String filter ="";
    18    Extent extent =pm.getExtent  
                (Book.class,  false);
    19    Query qry = pm.newQuery 
                (extent, filter);
    20    return (Collection)qry.execute();
    21  }
    22
    23  public Book  getBook(String name) {
    24    PersistenceManager pm = getPM ();				
    25    String filter="nm==\"" + 
                        name  +"\"";
    26    Extent extent = pm.getExtent 
                  (Book.class,  false);
    27    Query qry = pm.newQuery 
                  (extent, filter);
    28    Collection c = 
                (Collection) qry.execute ();
    29    return (Book) c.iterator().next();
    30   }
    31 }
    
    

    Additional Code For This Article (~ 7.78 KB ~zip format )

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: [email protected]

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.