2.2. Kodo JDO Tutorial

2.2.1. The Pet Shop
2.2.1.1. Included Files
2.2.1.2. Important Utilities
2.2.2. Getting Started
2.2.2.1. Configuring the Datastore
2.2.3. Inventory Maintenance
2.2.3.1. Persisting Objects
2.2.3.2. Deleting Objects
2.2.4. Inventory Growth
2.2.5. Behavioral Analysis
2.2.5.1. Complex Queries
2.2.6. Extra Features

In this tutorial you will become familiar with the basic tools and development processes under Kodo by creating a simple JDO application.

2.2.1. The Pet Shop

Imagine that you have decided to create a software toolkit to be used by pet shop operators. This toolkit must provide a number of solutions to common problems encountered at pet shops. Industry analysts indicate that the three most desired features are inventory maintenance, inventory growth simulation, and behavioral analysis. Not one to question the sage advice of experts, you choose to attack these three problems first.

According to the aforementioned experts, most pet shops focus on three types of animals only: dogs, rabbits, and snakes. This ontology suggests the following class hierarchy:

                       Animal
                         ^
                         |
               +--------------------+
               |         |          |
              Dog      Rabbit     Snake
   	    

2.2.1.1. Included Files

We have provided an implementation of Animal and Dog classes, plus some helper classes and files to create the initial schema and populate the database with some sample dogs. Let's take a closer look at these classes.

2.2.1.2. Important Utilities

  • java: Runs main methods in specified Java classes.

  • javac: Compiles .java files into .class files that can be executed by java.

  • kodoc: Runs the Kodo enhancer against the specified classes. More information is available in Section 5.2, “Enhancement” of the Reference Guide.

  • mappingtool: A utility that can be used to create and maintain the object-relational mappings and schema of all persistent classes in a JDBC-compliant datastore. This functionality allows the underlying mappings and schema to be easily kept up-to-date with the Java classes in the system. See Chapter 7, Mapping of the Reference Guide for more information.

2.2.2. Getting Started

Let's compile the initial classes and see them in action. To do so, we must compile the .java files, as we would with any Java project, and then pass the resulting classes through the JDO enhancer:

[Note]Note

Be sure that your CLASSPATH is set correctly. Detailed information on which libraries are needed can be found in Appendix 6, Development and Runtime Libraries. Note, also, that your Kodo install directory should be in the CLASSPATH, as the tutorial classes are located in the tutorial/jdo directory under your Kodo install directory, and are in the tutorial.jdo package.

  1. Change to the tutorial/jdo directory.

    All examples throughout the tutorial assume that you are in this directory.

  2. Examine Animal.java, Dog.java , and SeedDatabase.java

    These files are good examples of the simplicity JDO engenders. As noted earlier, persisting an object or manipulating an object's persistent data requires almost no JDO-specific code. For a very simple example of creating persistent objects, please see the main method of SeedDatabase.java . Note the objects are created with normal Java constructors. The files Animal.java and Dog.java are also good examples of how JDO allows you to manipulate persistent data without writing any specific JDO code.

  3. Compile the .java files.

    javac *.java
    

    You can use any java compiler instead of javac.

  4. Enhance the JDO classes.

    kodoc -p jdo.properties package.jdo
    

    This step runs the Kodo enhancer on the package.jdo file mentioned above. The package.jdo file contains an enumeration of all the classes that should be JDO enhanced. The Kodo enhancer will examine the metadata defined in this file and enhance all classes listed in it appropriately. See Section 5.2, “Enhancement” of the Reference Guide for more information on the JDO enhancer, including how to use Kodo's automatic runtime enhancement.

    [Note]Note

    The -p flag points the enhancer to your jdo.properties configuration file. All Kodo JDO tools look for default configuration in a resource called kodo.properties or META-INF/kodo.properties. Thus you can avoid passing the -p argument to tools by using this configuration file name in place of jdo.properties. See Chapter 2, Configuration in the Reference Guide for details on Kodo configuration.

2.2.2.1. Configuring the Datastore

Now that we've compiled the source files and enhanced the JDO classes, we're ready to set up the database. For more information on how to connect to a different database or how to add support for other databases, see Chapter 4, JDBC of the Reference Guide.

  1. Create the object-relational mappings and database schema.

    mappingtool -p jdo.properties package.jdo
    

    This command creates object-relational mappings for the classes listed in package.jdo, and at the same time propagates the necessary schema to the database configured in jdo.properties . If you are using the default Hypersonic SQL setup, the first time you run the mapping tool Hypersonic will create tutorial_database.properties and tutorial_database.script database files in your current directory. To delete the database, just delete these files.

    By default, JDO stores object-relational mapping information in your .jdo files. As you will see in the Reverse Mapping Tool Tutorial, you can also configure Kodo to store object-relational mappings in separate files or in a database table. Chapter 7, Mapping of the Reference Guide describes your mapping options in detail.

    If you'd like to see the mapping information Kodo has just created, examine the package.jdo file. Chapter 15, Mapping Metadata of the JDO Overview will help you understand mapping XML, should the need ever arise. Most Kodo development does not require any knowledge of mappings.

    If you are curious, you can also view view the schema Kodo created for the tutorial classes with Kodo's schema tool:

    schematool -p jdo.properties -a reflect -f tmp.schema
    

    This will create a tmp.schema file with an XML representation of the database schema. The XML should be self explanatory; see Section 4.15, “XML Schema Format” of the Reference Guide for details. You may delete the tmp.schema file before proceeding.

  2. Populate the database with sample data.

    java tutorial.jdo.SeedDatabase
    

Congratulations! You have now created a JDO-accessible persistent store, and seeded it with some sample data.

2.2.3. Inventory Maintenance

The most important element of a successful pet store product, say the experts, is an inventory maintenance mechanism. So, let's work on the Animal and Dog classes a bit to permit user interaction with the database.

This chapter should familiarize you with some of the basics of the JDO specification and the mechanics of compiling and enhancing persistence-capable objects. You will also become familiar with the mapping tool for propagating the JDO schema into the database.

First, let's add some code to AnimalMaintenance.java that allows us to examine the animals currently in the database.

  1. Add code to AnimalMaintenance.java.

    Modify the getAnimals method of AnimalMaintenance.java to look like this:

        /**
         * Return a list of animals that match the specified query filter.
         *
         * @param filter the JDO filter to apply to the query
         * @param cls the class of animal to query on
         * @param pm the PersistenceManager to obtain the query from
         */
        public static List getAnimals(String filter, Class cls, 
            PersistenceManager pm) {
            // Execute a query for the specified class and filter.
            Query query = pm.newQuery(cls, filter);
            return (List) query.execute();
        }
    
  2. Compile AnimalMaintenance.java.

    javac AnimalMaintenance.java
    
  3. Take a look at the animals in the database.

    java tutorial.jdo.AnimalMaintenance list Animal
    

    Notice that list optionally takes a query filter. Let's explore the database some more, this time using filters:

    java tutorial.jdo.AnimalMaintenance list Animal "name == 'Binney'"
    java tutorial.jdo.AnimalMaintenance list Animal "price <= 50"
    

    The JDO query language is designed to look and behave much like boolean expressions in Java. The name and price fields identified in the above queries map to the member fields of those names in tutorial.jdo.Animal. More details on JDO query syntax is available in Chapter 11, Query of the JDO Overview.

Great! Now that we can see the contents of the database, let's add some code that lets us add and remove animals.

2.2.3.1. Persisting Objects

As new dogs are born or acquired, the store owner will need to add new records to the inventory database. In this section, we'll write the code to handle additions through the tutorial.jdo.AnimalMaintenance class.

This section will familiarize you with the mechanism for storing persistence-capable objects in a JDO persistence manager. We will create a new dog, obtain a Transaction from a PersistenceManager, and, within the transaction, make the new dog object persistent.

tutorial.jdo.AnimalMaintenance provides a reflection-based facility for creating any type of animal, provided that the animal has a two-argument constructor whose first argument corresponds to the name of the animal and whose second argument is an implementation-specific primitive. This reflection-based system is in place to keep this tutorial short and remove repetitive creation mechanisms. It is not a required part of the JDO specification.

  1. Add the following code to AnimalMaintenance.java.

    Modify the persistObject method of AnimalMaintenance.java to look like this:

        /**
         * Performs the actual JDO work of putting <code>object</code>
         * into the datastore.
         *
         * @param object the object to persist in the datastore
         */
        public static void persistObject(Object object) {
            // Get a PersistenceManagerFactory and PersistenceManager.
            PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory 
                ("META-INF/jdo.properties");
            PersistenceManager pm = pmf.getPersistenceManager();
    
            // Obtain a transaction and mark the beginning
            // of the unit of work boundary.
            Transaction transaction = pm.currentTransaction();
            transaction.begin();
    
            pm.makePersistent(object);
    
            // Mark the end of the unit of work boundary,
            // and record all inserts in the database.
            transaction.commit();
    
            System.out.println("Added " + object);
    
            // Close the PersistenceManager and PersistenceManagerFactory.
            pm.close();
            pmf.close();
        }
    
  2. Recompile AnimalMaintenance.java.

    javac AnimalMaintenance.java
    

You now have a mechanism for adding new dogs to the database. Go ahead and add some by running java tutorial.jdo.AnimalMaintenance add Dog <name> <price> For example:

java tutorial.jdo.AnimalMaintenance add Dog Fluffy 35

You can view the contents of the database with:

java tutorial.jdo.AnimalMaintenance list Dog

2.2.3.2. Deleting Objects

What if someone decides to buy one of the dogs? The store owner will need to remove that animal from the database, since it is no longer in the inventory.

This section demonstrates how to remove data from the datastore.

  1. Add the following code to AnimalMaintenance.java .

    Modify the deleteObjects method of AnimalMaintenance.java to look like this:

        /**
         * Performs the actual JDO work of removing 
         * <code>objects</code> from the datastore.
         *
         * @param objects the objects to persist in the datastore
         * @param pm the PersistenceManager to delete with
         */
        public static void deleteObjects(Collection objects, 
            PersistenceManager pm) {
            // Obtain a transaction and mark the beginning of the
            // unit of work boundary.
            Transaction transaction = pm.currentTransaction();
            transaction.begin();
    
            for (Iterator iter = objects.iterator(); iter.hasNext(); )
                System.out.println("Removed animal: " + iter.next());
    
            // This method removes the objects in 'objects' from the datastore. 
            pm.deletePersistentAll(objects);
    
            // Mark the end of the unit of work boundary, and record all
            // deletes in the database.
            transaction.commit();
        }
    
  2. Recompile AnimalMaintenance.java.

    javac AnimalMaintenance.java
    
  3. Remove some animals from the database.

    java tutorial.jdo.AnimalMaintenance remove Animal <query>
    

    Where <query> is a query string like those used for listing animals above.

All right. We now have a basic pet shop inventory management system. From this base, we will add some of the more advanced features suggested by our industry experts.

2.2.4. Inventory Growth

Now that we have the basic pet store framework in place, let's add support for the next pet in our list: the rabbit. The rabbit is a bit different than the dog; pet stores sell them all for the same price, but gender is critically important since rabbits reproduce rather easily and quickly. Let's put together a class representing a rabbit.

In this chapter, you will see some more queries and write a bidirectional relation between objects.

Provided with this tutorial is a file called Rabbit.java which contains a sample Rabbit implementation. Let's get it compiled and loaded:

  1. Examine and compile Rabbit.java.

    javac Rabbit.java
    
  2. Add an entry for Rabbit to package.jdo.

    The Rabbit class above contains a bidirectional relationship between parents and children. From the Java side of things, a bidirectional relationship is simply a pair of fields that are conceptually linked. There is no special Java work necessary to express bidirectionality. However, you must identify the relationship as bidirectional in the JDO metadata for the mapping tool to create the most efficient schema. Insert the snippet below into the package.jdo file. It identifies both the type of data in the collection (the element-type attribute) and the name of the other side of the relation. The mapped-by attribute for identifying bidirectional relations is part of JDOR, a subset of the JDO specification for relational databases. For more information on JDOR, consult Chapter 14, JDOR of the JDO Overview.

    Add the following code immediately before the "</package>" line in the package.jdo file.

    <class name="Rabbit">
        <field name="parents">
            <collection element-type="Rabbit"/>
        </field>
        <field name="children" mapped-by="parents">
            <collection element-type="Rabbit"/>
        </field>
    </class>
    
  3. Enhance the Rabbit class.

    kodoc -p jdo.properties Rabbit.java
    
  4. Refresh the object-relational mappings and database schema.

    mappingtool -p jdo.properties Rabbit.java
    

Now that we have a Rabbit class, let's get some preliminary rabbit data into the database.

  1. Create some rabbits.

    Run the following commands a few times to add some male and female rabbits to the database:

    java tutorial.jdo.AnimalMaintenance add Rabbit <name> false
    java tutorial.jdo.AnimalMaintenance add Rabbit <name> true
    

    Now run some breeding iterations.

    java tutorial.jdo.Rabbit breed 2
    
  2. Look at your new rabbits.

    java tutorial.jdo.AnimalMaintenance list Rabbit
    java tutorial.jdo.AnimalMaintenance details Rabbit ""
    

2.2.5. Behavioral Analysis

Often, pet stores sell snakes as well as rabbits and dogs. Pet stores are primarily concerned with a snake's length; much like rabbits, pet store operators usually sell them all for a flat rate.

This chapter demonstrates more queries, schema manipulation, and additional relation types.

Provided with this tutorial is a file called Snake.java which contains a sample Snake implementation. Let's get it compiled and loaded:

  1. Examine and compile Snake.java.

    javac Snake.java
    
  2. Add tutorial.jdo.Snake to package.jdo.

    <class name="Snake"/>
    
  3. Enhance the class.

    kodoc -p jdo.properties Snake.java
    
  4. Refresh the mappings and database.

    As we have created a new persistence-capable class, we must map it to the database and change the schema to match. So run the mapping tool:

    mappingtool -p jdo.properties Snake.java
    

Once you have compiled everything, add a few snakes to the database using:

java tutorial.jdo.AnimalMaintenance add Snake <name> <length>

Where <name> is the name and <length> is the length in feet for the new snake. To see the new snakes in the database, run:

java tutorial.jdo.AnimalMaintenance list Snake

Unfortunately for the massively developing rabbit population, snakes often eat rabbits. Any good inventory system should be able to capture this behavior. So, let's add some code to Snake.java to support the snake's eating behavior.

First, let's modify Snake.java to contain a list of eaten rabbits.

  1. Add the following code snippet to Snake.java .

        // *** Add this member variable declaration. ***
        private Set giTract = new HashSet();
    
        ...
    
        // *** Modify toString(boolean) to output the giTract list. ***
        public String toString(boolean detailed) {
            StringBuffer buf = new StringBuffer(1024);
            buf.append("Snake ").append(getName());
    
            if (detailed) {
                buf.append(" (").append(length).append(" feet long) sells for ");
                buf.append(getPrice()).append(" dollars.");
                buf.append("  Its gastrointestinal tract contains:\n");
                for (Iterator iter = giTract.iterator(); iter.hasNext();)
                    buf.append("\t").append(iter.next()).append("\n");
            }
            else
                buf.append("; ate " + giTract.size() + " rabbits.");
    
            return buf.toString();
        }
    
        ...
    
        // *** Add these methods. ***
        /**
         *  Kills the specified rabbit and eats it.
         */
        public void eat(Rabbit dinner) {
            // Consume the rabbit.
            dinner.kill();
            dinner.eater = this;
            giTract.add(dinner);
            System.out.println("Snake " + getName() + " ate rabbit " 
                + dinner.getName() + ".");
        }
    
        /**
         *  Locates the specified snake and tells it to eat a rabbit.
         */
        public static void eat(String filter) {
            PersistenceManagerFactory pmf = JDOHelper.
                getPersistenceManagerFactory("META-INF/jdo.properties");
            PersistenceManager pm = pmf.getPersistenceManager();
            Transaction transaction = pm.currentTransaction();
            transaction.begin();
    
            // Find the desired snake(s) in the datastore.
            Query query = pm.newQuery(Snake.class, filter);
            List results = (List) query.execute();
            if (results.isEmpty()) {
                System.out.println("No snakes matching '" + filter + "' found");
                return;
            }
    
            Query uneatenQuery = pm.newQuery(Rabbit.class, "isDead == false");
            Random random = new Random();
            for (Iterator iter = results.iterator(); iter.hasNext();) {
                // Run a query for a rabbit whose 'isDead' field indicates
                // that it is alive.
                List menu = (List) uneatenQuery.execute();
                if (menu.isEmpty()) {
                    System.out.println("No live rabbits in DB.");
                    break;
                }
    
                // Select a random rabbit from the list.
                Rabbit dinner = (Rabbit) menu.get(random.nextInt(menu.size()));
        
                // Perform the eating.
                Snake snake = (Snake) iter.next();
                System.out.println(snake + " is eating:");
                snake.eat(dinner);
            }
    
            transaction.commit();
            pm.close();
            pmf.close();
        }
    
    
        public static void main(String [] args) {
            if (args.length == 2 && args[0].equals("eat")) {
                eat(args[1]);
                return;
            }
    
            // If we get here, something went wrong.
            System.out.println("Usage:");
            System.out.println("  java tutorial.jdo.Snake eat 'snakequery'");
        }
    
  2. Add an eater field to Rabbit.java.

    Notice that we are making this field protected, and that we set it from the Snake class. This demonstrates that it is possible to directly access public or protected persistent fields in other classes. This is not recommended practice, however, because it means that all classes that access this field directly must be JDO enhanced, even if they are not persistence-capable.

    Add the following member variable to Rabbit.java :

        protected Snake eater;
    
  3. Add metadata to package.jdo.

    Notice the giTract declaration in Snake.java: it is a simple Java collection declaration. As with the parents and children sets in Rabbit.java, we augment this declaration with JDO metadata.

    <class name="Snake">
        ...
        <field name="giTract" mapped-by="eater">
            <collection element-type="Rabbit"/>
        </field>
    </class>
    

    Note that we specified a mapped-by attribute in this example. This is because the relation is bidirectional; that is, the rabbit has knowledge of which snake ate it. We could have left out the eater field and instead created a standard unidirectional relation. The metadata might have looked like this:

    <class name="Snake">
        <field name="giTract">
            <collection element-type="Rabbit"/>
        </field>
    </class>
    

    For more information on types of relations, see Section 15.11, “Field Mapping” of the JDO Overview.

  4. Compile Snake.java and Rabbit.java and enhance the classes.

    javac Snake.java Rabbit.java
    kodoc -p jdo.properties Snake.java Rabbit.java
    
  5. Refresh the mappings and database.

    mappingtool -p jdo.properties Snake.java Rabbit.java
    

Now, experiment with the following commands:

java tutorial.jdo.Snake eat ""
java tutorial.jdo.AnimalMaintenance details Snake ""

2.2.5.1. Complex Queries

Imagine that one of the snakes in the database was named Killer. To find out which rabbits Killer ate, we could run either of the following two queries:

java tutorial.jdo.AnimalMaintenance details Snake "name == 'Killer'"
java tutorial.jdo.AnimalMaintenance list Rabbit "eater.name == 'Killer'"

The first query is snake-centric - the query runs against the Snake class, looking for all snakes named Killer and providing a detailed listing of them. The second is rabbit-centric - it examines the rabbits in the database for instances whose eater is named Killer. This second query demonstrates the that simple Java 'dot' syntax is used when traversing a relation field in a query.

It is also possible to traverse collection fields. Imagine that there was a rabbit called Roger in the datastore and that one of the snakes ate it. In order to determine who ate Roger Rabbit, you could run a query like this:

java tutorial.jdo.AnimalMaintenance details Snake "giTract.contains(rabbit) && rabbit.name == 'Roger'"

Note the use of the rabbit variable above. Variables are tokens that represent any element of the collection that contains them. So in our query, the rabbit variable stands for any rabbit in the snake's giTract collection. We could have called the variable anything; just as in Java, JDO query variables do not have to follow a set naming pattern.

2.2.6. Extra Features

Congratulations! You are now the proud author of a pet store inventory suite. Now that you have all the major features of the pet store software implemented, it's time to add some extra features. You're on your own; think of some features that you think a pet store should have, or just explore the features of JDO.

Here are a couple of suggestions to get you started:

  • Animal pricing.

    Modify Animal to contain an inventory cost and a resale price. Calculate the real dollar amount eaten by the snakes (the sum of the inventory costs of all the consumed rabbits), and the cost assuming that all the eaten rabbits would have been sold had they been alive. Ignore the fact that the rabbits, had they lived, would have created more rabbits, and the implications of the reduced food costs due to the not-quite-as-hungry snakes and the smaller number of rabbits.

  • Dog categorization.

    Modify Dog to have a relation to a new class called Breed, which contains a name identifying the breed of the dog and a description of the breed. Put together an admin tool for breeds and for associating dogs and breeds.

 

Skip navigation bar   Back to Top