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 three major APIs. The PersistenceBroker, the ODMG
implementation, and the JDO 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 org.apache.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.