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.

see it running...

To see the tutorial application at work you first have to compile the sources by executing build[.sh] main in the ojb toplevel directory.
Next you have to setup the demo database by executing build[.sh] tests.

Now you can start the tutorial application by executing tutorial2[.sh] in the ojb toplevel directory.

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 consists of interfaces defining the API and of some Exception classes. The OJB implementation of the ODMG API resides in the package ojb.odmg. 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.

We then have to obtain a write lock on the new Object. This will inform the transaction about the new Object, so that it may perform the correct persistence operations on transaction commit. The locking is also necessary to have a proper isolation of concurrent transactions. Learn more about locking here.

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()
    {
        // 1. this will be our new object
        Product newProduct = new Product();
        // 2. 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;
            // 3. open transaction
            tx = odmg.newTransaction();
            tx.begin();

            // 4. acquire write lock on new object
            tx.lock(newProduct, tx.WRITE);

            // 5. commit transaction
            tx.commit();
    }

Updating Objects

The UseCase UCEditProduct allows the user to select one of the existing products and to edit it. The user enters the products unique id and the ODMG server tries to lookup the respective object. This lookup is necessary as our application does not hold a list of all products. The product is then locked for write access by the ongoing transaction and edited by the user. After that the transaction is commited and performs an update on the database.

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

        // We don't have a reference to the selected Product.
        // So first we have to lookup the object.

        // 1. build oql query to select product by id:
        String oqlQuery = "select del from " + Product.class.getName() + " where _id = " + id;

        Database db = odmg.getDatabase(null); // the current DB
        Transaction tx = null;
        try
        {
            // 2. start transaction
            tx = odmg.newTransaction();
            tx.begin();

            // 3. lookup the product specified by query
            OQLQuery query = odmg.newOQLQuery();
            query.create(oqlQuery);
            DList result = (DList) query.execute();
            Product toBeEdited = (Product) result.get(0);

            // 4. lock the product for write access
            tx.lock(toBeEdited, tx.WRITE);

            // 5. Edit the product entry
            System.out.println("please edit existing product");
            in = readLineWithMessage("enter name (was " + toBeEdited.getName() + "):");
            toBeEdited.setName(in);
            in = readLineWithMessage("enter price (was " + toBeEdited.getPrice() + "):");
            toBeEdited.setPrice(Double.parseDouble(in));
            in = readLineWithMessage("enter available stock (was " + toBeEdited.getStock() + "):");
            toBeEdited.setStock(Integer.parseInt(in));

            // 6. commit transaction
            tx.commit();
        }
        catch (Throwable t)
        {
            // rollback in case of errors
            tx.abort();
            t.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 OQL statement that will select the proper product entry.

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

In step three we perform an OQL query with the statement from step one. The result of the query 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);

        // We don't have a reference to the selected Product.
        // So first we have to lookup the object.

        // 1. build oql query to select product by id:
        String oqlQuery = "select del from " + Product.class.getName() + " where _id = " + id;

        Database db = odmg.getDatabase(null); // the current DB
        Transaction tx = null;
        try
        {
            // 2. start transaction
            tx = odmg.newTransaction();
            tx.begin();

            // 3. lookup the product specified by query
            OQLQuery query = odmg.newOQLQuery();
            query.create(oqlQuery);
            DList result = (DList) query.execute();
            Product toBeDeleted = (Product) result.get(0);

            // 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$