DODS Project
About DODS
Project Mail Lists
FAQs




DODS FAQs - How do I use the code generated by DODS?
dods.gif (11357 bytes)This tutorial describes working with the SQL and classes that DODS generates. It does not talk about using the GUI, so it will remain accurate no matter what the new GUI looks like.

Describe Your Database Tables 
As an example, let's say your application needs to store and retrieve information about cars, car dealers, and car owners. 

First, you have to describe the database tables your app will need. Let's say your tables look like this:

Table name Column name Data type Column description
Dealer Name String Name of car dealership
Owner Name String Name of car owner
  Age int Age of car owner
Car LicensePlate String License plate # of car

Then, you have to write DOML file in which are described tables of the application.


Files DODS Generates
For every  table from DOML file is generated one XML file.That XML file is used as a base for generating Java files. DODS will generate the following SQL files and Java classes:

File generated Purpose
DealerSQL.sql The CREATE TABLE statement for the Dealer table.
DealerDO.java Represents a new/existing row in the Dealer table.
DealerQuery.java Retrieves DealerDO objects for rows in the Dealer table.
OwnerSQL.sql The CREATE TABLE statement for the Owner table.
OwnerDO.java Represents a new/existing row in the Owner table.
OwnerQuery.java Retrieves DealerDO objects for rows in the Owner table.
CarSQL.sql The CREATE TABLE statement for the Car table.
CarDO.java Represents a new/existing row in the Car table.
CarQuery.java Retrieves DealerDO objects for rows in the Car table.

Assume that Dealers only sell new cars. That is, a dealer sells many cars, but only sells a given car once.To represent this one-to-many relationship, you would add a reference to the Car table:

Table name Column name Java type DB Type Column description
Car LicensePlate String CHAR License plate # of car
  Dealer DealerDO REFERENCE foreign key of Dealer

People can own multiple (0 to many) cars at a time. People can also sell cars to each other, so over time a given car can have multiple (1 to many) owners. To represent this many-to-many relationship between cars and owners, you would create a new table:

Table name Column name Java type DB Type Column description
CarOwner Car CarDO REFERENCE foreign key of Car
  Owner OwnerDO REFERENCE foreign key of Owner
  IsCurrent boolean BIT true if current owner

(A table that represents a many-to-many relationship is sometimes called an 'association table.')

DODS would generate these files for this new table:

File generated Purpose
CarOwnerSQL.sql CREATE TABLE statement for CarOwner table
CarOwnerDO.java An instance of the many-to-many relationship
CarOwnerQuery.java Retrieves instances of the many-to-many relationship

Creating the Tables
To use the Java classes generated by DODS, you first use the .sql files to create the tables. To make this easier, DODS creates the file

File generated Purpose
create_tables.sql CREATE TABLE statements for all tables, proper sequence

which is a concatenation of the files

  • DealerSQL.sql
  • OwnerSQL.sql
  • CarSQL.sql
  • CarOwnerSQL.sql

You would pipe (or cut-and-paste) the contents of create_tables.sql into the interactive SQL tool provided by your database vendor.


Using the DO Classes to Create Data
After the tables are created in the database you could begin using the Java classes generated by DODS.

This code snippet creates a dealer, a car, an owner, and establishes relationships between them.

    // create a dealer
    DealerDO autoWorld = DealerDO.createVirgin();
    autoWorld.setName( "Bob's Auto World" );

    // create a car
    CarDO lemon = CarDO.createVirgin();
    lemon.setLicensePlate( "ABC123" );
    lemon.setDealer( autoWorld );

    // create an owner
    OwnerDO joe = OwnerDO.createVirgin();
    joe.setName( "Joe Lanechange" );
    joe.setAge( 16 );

    // joe is the current owner of the car
    CarOwnerDO currentOwner = CarOwnerDO.createVirgin();
    currentOwner.setCar( lemon );
    currentOwner.setOwner( joe );
    currentOwner.setIsCurrent( true );
    currentOwner.save();

The call to

    currentOwner.save();

does several things:

  1. writes (saves) the OwnerDO (joe) to the Owner table in the database,
  2. writes (saves) the CarDO (lemon) to the Car table,
  3. writes the CarOwnerDO object (currentOwner) into the CarOwner table.

Because CarOwnerDO (currentOwner) holds references to other DO objects (CarDO & OwnerDO), it must write (save) them to the database first, before it can save itself.

Note that because CarDO (lemon) holds a reference to DealerDO (autoWorld),
when currentOwner.save() calls lemon.save(),
lemon.save() in turn calls autoWorld.save().
So, the dealer gets saved, too.

You could also call

    autoWorld.save();
    lemon.save();
    joe.save();
    currentOwner.save();

explicitly. The DO objects remember whether they are in sync with the database, and their save() methods do nothing if they are already saved. So, if you saved all the DOs explicitly, then the call to currentOwner.save(); would only perform step #3 where it saves itself to the CarOwner table.


Using the Query Classes to Retrieve Data
Now that there is data in the tables, you can use the Query classes to retrieve the objects. This code snippet finds the DealerDO created above:

    DealerQuery dealerQuery = new DealerQuery();
    dealerQuery.setQueryName( "Bob's Auto World" );
    DealerDO autoWorld = dealerQuery.getNextDO();

This code snippet returns the Cars sold by that Dealer:

    CarDO[] cars = autoWorld.getCarDOArray();

Note that the method DealerDO.getCarDOArray() made it easy.
This snippet would do the same thing:

    CarQuery carQuery = new CarQuery();
    carQuery.setQueryDealer( autoWorld );
    CarDO[] cars = carQuery.getDOArray();

You can also find the lemon by its license plate (similar to the DealerQuery snippet above):

    CarQuery carQuery = new CarQuery();
    carQuery.setQueryLicensePlate( "ABC123" );
    carQuery.requireUniqueInstance();  // plate numbers are unique
    CarDO lemon = carQuery.getNextDO();

DODS generates some convenience methods, based on inter-table relationships. To retrieve all CarOwner rows, representing all past and current owners of the lemon:

    CarOwnerDO[] lemonOwners = lemon.getOwnerDOArray_via_CarOwner();

To find the current owner of the lemon:

    CarOwnerQuery carOwnerQuery = new CarOwnerQuery();
    carOwnerQuery.setQueryCar( lemon );
    carOwnerQuery.setQueryIsCurrentOwner( true );
    carOwnerQuery.requireUniqueInstance(); // each car has only 1 current owner
    CarOwnerDO lemonCarOwner = carOwnerQuery.getNextDO();
    OwnerDO lemonOwner = lemonCarOwner.getOwner();
    System.err.println( "The current owner is " + lemonOwner.getName() );

Using the DO Classes to Delete Data
To remove Joe from the database:

    OwnerQuery ownerQuery = new OwnerQuery();
    ownerQuery.setQueryName( "Joe Lanechange" );
    OwnerDO joe = ownerQuery.getNextDO();
    joe.delete();

The row for Joe is removed from the Owner table.

Note that since you created a row in the CarOwner table that referenced the row for Joe in the Owner table, that row in the  CarOwner table must also be deleted. Otherwise the database will have lost referential integrity: the CarOwner table would have a reference to an Owner row that no longer exists.

Some database vendors (e.g. Oracle) have a feature called "cascading delete". The database detects a dangling reference like the one in the CarOwner table, and automatically deletes that row as well. When using DODS to describe your tables, you specify the database vendor. If that vendor does not have the "cascading delete" feature, DODS will generate extra code in the DO.delete() method to perform the cascading delete to preserve referential integrity in the database.


Using Comparison Operators
It's easy to find Owners who are exactly 16 years old:

    OwnerQuery ownerQuery = new OwnerQuery();
    ownerQuery.setQueryAge( 16 );
    OwnerDO[] troubleMakers = ownerQuery.getDOArray();

To find Owners who are younger than 21:

    OwnerQuery ownerQuery = new OwnerQuery();
    ownerQuery.setQueryAge( 21, QueryBuilder.LESS_THAN );
    /*
     * This produces the SQL query:
     *
     *      SELECT Owner.* FROM Owner
     *      WHERE  Owner.Age < 21
     */
    OwnerDO[] youngDrivers = ownerQuery.getDOArray();

Every Query class uses a QueryBuilder object. Calls like

    ownerQuery.setQueryAge( 21, QueryBuilder.LESS_THAN );

utilize QueryBuilder comparison operators. Here's the full list of QueryBuilder comparison operators:

    EQUAL
    NOT_EQUAL
    LESS_THAN
    LESS_THAN_OR_EQUAL
    GREATER_THAN
    GREATER_THAN_OR_EQUAL
    IS_NULL
    IS_NOT_NULL
    // These are only for use with strings:
    CASE_INSENSITIVE_EQUAL
    CASE_SENSITIVE_CONTAINS
    CASE_INSENSITIVE_CONTAINS
    CASE_SENSITIVE_STARTS_WITH
    CASE_INSENSITIVE_STARTS_WITH    // example usage below
    CASE_SENSITIVE_ENDS_WITH
    CASE_INSENSITIVE_ENDS_WITH

Using QueryBuilder for Advanced Queries
You can access the QueryBuilder object used by your Query to add SQL "WHERE" clauses to perform more sophisticated searches.

To find Owners who are 16 years old or older, and who have bought cars that were sold by a dealer whose name starts with "Bob":

    OwnerQuery ownerQuery = new OwnerQuery();
    ownerQuery.setQueryAge( 16, QueryBuilder.GREATER_THAN_OR_EQUAL );
    QueryBuilder qb = ownerQuery.getQueryBuilder();
    qb.addWhere( OwnerDO.PrimaryKey, CarOwnerDO.Owner );
    qb.addWhere( CarOwnerDO.Car,     CarDO.PrimaryKey );
    qb.addWhere( CarDO.Dealer,       DealerDO.PrimaryKey );
    qb.addWhere( DealerDO.Name,      "BOB", 
                                QueryBuilder.CASE_INSENSITIVE_STARTS_WITH );
    OwnerDO[] bobsCustomers = ownerQuery.getDOArray();
    /*
     * The code above uses a mix of setQueryXxx() methods
     * and QueryBuilder.addWhere() methods.
     * It produces this SQL query which JOINs the necessary tables:
     *
     *      SELECT Owner.* FROM Owner, CarOwner, Car, Dealer
     *      WHERE  Owner.Age             >=   16
     *      AND    Owner.oid             =    CarOwner.Owner
     *      AND    CarOwner.Car          =    Car.oid
     *      AND    Car.Dealer            =    Dealer.oid
     *      AND    LOWER( Dealer.Name )  LIKE "bob%"
     *
     * For each column in your table, 
     * DODS creates a static RDBColumn member in your DO class.
     * These members are used in the calls to addWhere().
     *
     * For each table (see any .sql file generated by DODS), 
     * DODS adds an 'oid' column as the primary key.
     * So, OwnerDO.PrimaryKey is the RDBColumn member
     * for the 'oid' column in the Owner table.
     *
     * QueryBuilder.CASE_INSENSITIVE_STARTS_WITH is a comparison operator
     * which produces string comparison clauses using SQL's LIKE operator.
     */

Familiarity with SQL will help you compose advanced queries.


Using QueryBuilder Alone, and Debugging Queries
Sometimes you just need the values from one (or a few) columns in some tables. An example is when you are generating a report, and you don't need DO objects because you won't be saving any changes to the data. In these cases, QueryBuilder can be used without Query classes.

To generate a report listing the license plate of each Car, the Dealer who originally sold the Car, and every Onwer of the Car:

    /*
     * create a QueryBuilder that will retrieve the desired fields...
     */
    QueryBuilder qb = new QueryBuilder();
    qb.select( CarDO.LicensePlate );
    qb.select( DealerDO.Name );
    qb.select( OwnerDO.Name );
    /*
     * compose the necessary JOINs...
     */
    qb.addWhere( OwnerDO.PrimaryKey, CarOwnerDO.Owner );
    qb.addWhere( CarOwnerDO.Car,     CarDO.PrimaryKey );
    qb.addWhere( CarDO.Dealer,       DealerDO.PrimaryKey );
    /*
     * retrieve the values for the desired fields...
     */
    RDBRow row;
    while ( null != ( row = qb.getNextRow() ) )
        System.err.println(
            "  Plate # =" + row.get( CarDO.LicensePlate ).getString() +
            "  Dealer  =" + row.get( DealerDO.Name      ).getString() +
            "  Owner   =" + row.get( OwnerDO.Name       ).getString() );

The QueryBuilder class has many addWhere... methods to help you assemble complex queries. Calling the method QueryBuilder.debug() will print the SQL that has been assembled before it is executed.


Important Notes and Warnings
Here are included several "gotchas" and related warnings associated with using the DODS-generated SQL directly, as described in this section.

  1. QueryBuilder does not work with the DataStruct Cache
    Given a table containing just a set of valid zipcodes (or other static data), you may want to load the entire table into memory when your application starts. In DODS you can specify that the ZipcodeDO class will be "cache-enabled". For static data, it is best to use "full" cache. Then, the ZipcodeDO class will have a static cache containing all ZipcodeDO objects.

    The ZipcodeQuery class will search the cache inside the ZipcodeDO class, rather than query the database. This can make your application run faster.

    However, it is important to understand that the cache is not a relational database, and the cache cannot support JOIN operations. So, anytime you call ZipcodeQuery.getQueryBuilder() the ZipcodeQuery object will assume your are setting up a complex query that the cache cannot support, and it send the query to the database, ignoring the cache.

For all the latest information on DODS, please refer to http://dods.enhydra.org/
Questions, comments, feedback? Let us know...