ObJectRelationalBridge
BRIDGING JAVA OBJECTS AND RELATIONAL DATABASES


ObJectRelationalBridge Tutorial Part 2:

Using the ObJectRelationalBridge ODMG API

Author: Thomas Mahler, april 2001

Introduction

This document demonstrates how to use the ObJectRelationalBridge (OJB) ODMG Api in a simple application scenario. The tutorial application implements a product catalog database with some basic use cases. The source code for the tutorial application is shipped with the OJB source distribution and resides in the package test.ojb.tutorial2.

The application scenario and the overall architecture have been introduced in the first part of this tutorial and won't be repeated here. The only modifications to the test.ojb.tutorial1 source code are due to the usage of the ODMG Api for persistence operations. The present document explains these modifications.

This document is not meant as a complete introduction to the ODMG standard and its Java binding in particular. Thus it does not cover important aspects like ODMG persistent collections. For a complete reference see the book "The Object Data Standard: ODMG 3.0", ed. R.G.G. Cattell, D.K. Barry, Morgan Kaufmann Publishers [ODMG30]. You will also find helpful material at the ODMG site.

Using the ODMG API in the UseCase implementations

In the first tutorial you learned that OJB provides two major APIs. The PersistenceBroker and the ODMG implementation. The first tutorial implemented the sample applications use cases with the PersistenceBroker API. This tutorial will show how the same use cases can be implemented using the ODMG API.

You will find the source for the ODMG API in the package org.odmg. The JavaDoc is here. The package contains of interfaces defining the API and of some Exception classes. The OJB implementation of the ODMG API resides in the package ojb.server. The JavaDoc is here.

Obtaining the ODMG Implementation Object

In order to access the functionalities of the ODMG API you have to deal with a special facade object that serves as the main entry point to all ODMG operations. This facade is specified by the Interface org.odmg.Implementation. It provides factory methods to obtain Database objects, Transaction objects, OQL Query objects and persistent collection objects. Any Vendor of an ODMG compliant product must provide a specific implementation of the org.odmg.Implementation interface. In fact "the only vendor-dependent line of code required in an ODMG application is the one that retrieves an ODMG implementation object from the vendor." [ODMG30, chapter 7] If you know how to use the ODMG API you only have to learn how to obtain the OJB specific Implementation object.

In our tutorial application the ODMG Implementation object is obtained in the constructor of the Application class and reached to the use case implementations for further usage:

public Application()
{
    // get odmg facade instance
    Implementation odmg = OJB.getInstance();
    Database db = odmg.newDatabase();
    //open database
    try
    {
        db.open(“repository.xml“, Database.OPEN_READ_WRITE);
    }
    catch (ODMGException ex)
    {
        ex.printStackTrace();
    }

    useCases = new Vector();
    useCases.add(new UCListAllProducts(odmg));
    useCases.add(new UCEnterNewProduct(odmg));
    useCases.add(new UCDeleteProduct(odmg));
    useCases.add(new UCQuitApplication(odmg));
}
 

The class ojb.server.OJB is the OJB specific org.odmg.Implementation and provides a static factory method getInstance(). The obtained instance is then used to create and open a org.odmg.Database. As you can see from the name of the Database, the OJB repository files are also used for the ODMG server. That means you can use one and the same mapping repository with the PersistenceBroker and with the ODMG API. There is also no need for any modifications to the persistent class Product. The Object / Relational mapping process is identical to that for the PersistenceBroker API. Thus I won't repeat the relevant section from the first tutorial here.

The Implementation object is reached to the constructors of the UseCases. These constructors store it in a protected attribute odmg for further usage.

Retrieving collections

The next thing we need to know is how this Implementation instance integrates into our persistence operations.

In the use case UCListAllProducts we have to retrieve a collection containing all product entries from the persistent store. To retrieve a collection containing objects matching some criteria we can use the Object Query Language (OQL) as specified by ODMG. OQL is quite similar to SQL but provides useful additions for objectoriented programming like path epressions. For example a query for persons that live in the city of Amsterdam could be defined in OQL like follows: "select person from Person where person.address.town='Amsterdam'". See chapter 4 of [ODMG30] for a complete OQL reference.

In our use case we want to select all persistent instances of the class Products, so our OQL Statement looks rather simple: "select allproducts from Products". To perform this OQLQuery we have to open a new ODMG Transaction first.

In the second step we have to obtain a OQLQuery object. As mentioned before we can use a factory method of the Implementation object for a new Query object.

In the third step we fill the query object with our special OQL statement.

In step four we perform the query and collect the results in a DList (one of the ODMG persistent collection classes). As we don't have any further db activity we can commit the transaction.

In the last step we iterate through the collection to print out each product matching our query.

    public void apply()
    {
        System.out.println("The list of available products:");
        try
        {
            // 1. open a transaction
            Transaction tx = odmg.newTransaction();
            tx.begin();

            // 2. get an OQLQuery object from the ODMG facade
            OQLQuery query = odmg.newOQLQuery();

            // 3. set the OQL select statement
            query.create("select allproducts from " + Product.class.getName());

            // 4. perform the query and store the result in a persistent Collection
            DList allProducts = (DList) query.execute();
            tx.commit();

            // 5. now iterate over the result to print each product
            java.util.Iterator iter = allProducts.iterator();
            while (iter.hasNext())
            {
                System.out.println(iter.next());
            }

        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
    }

Storing objects

Now we’ll have a look at the use case UCEnterNewProduct. It works as follows: first create a new object, then ask the user for the new product’s data (productname, price and available stock). These data is stored in the new objects attributes. This part is not different from the tutorial1 implementation.

But now we must store the newly created object in the persistent store by means of the ODMG API. With ODMG all persistence operations must happen within a transaction. So the first step is to ask the Implementation object for a fresh org.odmg.Transaction object to work with. The begin() method starts the transaction.

In the second step we obtain a unique value for the artificial primary key attribute _id. SequenceManager is a Utility class that can help you in maintaining unique id’s for primary key columns containing int values. In this case we ask for a unique id for Attribute “_id” of class Product. This step is not specific for ODMG and can be found in the tutorial1 implementation as well.

In the third step we create a unique name for the newly created product. ODMG provides a concept of "Named Roots". The Named Roots concept allows to store objects (typically root nodes of large object nets) under a freely chosen name. By looking up this name later one can retrieve the persistent object (net) from the underlying database. The named roots map works like a HashMap mapping names to root objects. (In fact OJB implements the Named root mechanims with a persistent HashMap.)

In step four we bind the newProduct object to the unique name chosen in step three. The bind method is part of the org.odmg.Database interface. With odmg.getDatabase(null) we can obtain the currently active Database (which we opened in the constructor of class Application).

In the last step we commit the transaction. All changes to objects touched by the transaction are now made persistent. As you will have noticed there is no need to explicitly „store“ objects as with the PersistenceBroker API. The Transaction object is responsible for tracking which objects have been modified and to choose the appropriate persistence operation on commit. (Internally the OJB transaction manager delegates all persistence operations to a PersistenceBroker instance.)

    public void apply()
    {
        // this will be our new object
        Product newProduct = new Product();
        // now read in all relevant information and fill the new object:
        System.out.println("please enter a new product");
        String in = readLineWithMessage("enter name:");
        newProduct.setName(in);
        in = readLineWithMessage("enter price:");
        newProduct.setPrice(Double.parseDouble(in));
        in = readLineWithMessage("enter available stock:");
        newProduct.setStock(Integer.parseInt(in));

        // now perform persistence operations
        Transaction tx = null;
        try
        {
            // 1. open transaction
            tx = odmg.newTransaction();
            tx.begin();

            // 2. we need a unique id for the new Object.
            newProduct.setId(SequenceManager.getUniqueId(Product.class, "_id"));

            // 3. create a unique name for the object to perfom named root lookups later;
            String name = "product_" + newProduct.getId();

            // 4. bind new object to this name for later reference:
            Database currentDB = odmg.getDatabase(null);
            currentDB.bind(newProduct, name);

            // 5. commit transaction
            tx.commit();
        }
        catch (ODMGException ex)
        {
            // if something went wrong: rollback
            tx.abort();
            System.out.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

Deleting Objects

 The UseCase UCDeleteProduct allows the user to select one of the existing products and to delete it from the persistent storage. The user enters the products unique id and the ODMG server tries to lookup the respective object by name. This lookup is necessary as our application does not hold a list of all products. The found object must then be deleted.

In the first step we form the name for the named roots lookup. In the UCEnterNewProduct we bound the product object in question to this name.

In the second step we obtain a fresh transaction and start it.

In step three we obtain the current Database object and perform a lookup by name. The result of the lookup(name) method is of type Object we thus have to apply a type cast.

In step four the retrieved Product is marked for deletion.

In the last step the transaction is commited. The Transaction recognizes the deletion mark for the object toBeDeleted and performs the appropriate action by delegating to the PersistenceBroker (i.e. perform a „DELETE FROM ...“).

   public void apply()
    {
        String in = readLineWithMessage("Delete Product with id:");
        int id = Integer.parseInt(in);

        // 1. build name for named roots lookup:
        String name = "product_" + id;

        Transaction tx = null;
        try
        {
            // 2. start transaction
            tx = odmg.newTransaction();
            tx.begin();

            // 3. lookup the product specified by named roots lookup
            Database currentDB = odmg.getDatabase(null); // the current DB
            Product toBeDeleted = (Product) currentDB.lookup(name);

            // 4. now mark object for deletion
            db.deletePersistent(toBeDeleted);

            // 5. commit transaction
            tx.commit();
        }
        catch (Throwable t)
        {
            // rollback in case of errors
            tx.abort();
            t.printStackTrace();
        }
    }



Conclusion

In this tutorial you learned to use the standard ODMG 3.0 API as implemented by the OJB system within a simple application scenario. I hope you found this tutorial helpful. Any comments are welcome.


$FOOTER$