Step 5: Writing a Sample Application

Congratulations, you have finally reached the fun the part of this tutorial. This is where you'll discover the power of Torque. Be warned, you'll never want to write another SQL statement ever again!

As mentioned earlier, when Torque created your object model, it created four Java classes for each table defined in your database schema. For example, the book table, defined in the database schema presented earlier, will result in the following classes: Book, BookPeer, BaseBook, and BaseBookPeer.

Book and BookPeer are subclasses of BaseBook and BaseBookPeer respectively. The two Base classes (BaseBook and BaseBookPeer) contain Torque-generated logic and should not be modified because Torque will overwrite your changes if you happen to generate your object model again. Any business logic that you might want to add should be placed in the Book and BookPeer classes. This is covered later in the tutorial.

You might be asking yourself, what is the difference between the Peer classes (BookPeer and BaseBookPeer) and their counterparts (Book and BaseBook), also known as Data Objects? The Peer classes “wrap” their associated database tables and provide static methods to manipulate those tables such as doSelect, doInsert, and doUpdate. Data Objects, on the other hand, “wrap” individual rows within those tables and provide getters/mutators for each column defined in those tables as well as the convenient save method. Both Peer and Data Objects have a one-to-one mapping to a table defined in your database schema. For a more in-depth discussion on Peers and Data Objects, refer to the Runtime relevant classes documentation. An example of adding logic to both the Peer and Data Objects is presented later in the tutorial.

Now that we've covered the basics of the object model that Torque generated for you, the rest of this section describes the Torque way of doing database inserts, selects, updates, and deletes illustrated with small segments of code. These segments of code are part of a sample application that is presented in full after a brief discussion on extending the object model classes. Finally, instructions on how to compile and run the application are detailed.

Inserting Rows

Inserting rows into your tables is easy with Torque. Simply instantiate a new Data Object of the appropriate class, set its properties using the mutators named after the table's columns, then invoke the Data Object's save method. Note: It is not necessary to set the object's primary key ID because Torque will do this for you automatically unless you've specified otherwise (see the Database Schema Configuration section above).

For example, to insert a new row in the author table (as defined in this tutorial's database schema): instantiate a new Author object, invoke the object's setFirstName and setLastName methods with appropriate values, then call the save method. That's it. The following is from the sample application:

Publisher addison = new Publisher();
addison.setName("Addison Wesley Professional");
addison.save();

Author bloch = new Author();
bloch.setFirstName("Joshua");
bloch.setLastName("Bloch");
bloch.save();

It is also possible to insert a row using the Peer class directly instead of invoking the save method of your Data Object. Recall, the Peer class provides static methods to perform operations on a table. One of these operations is the ability to insert rows via the doInsert method. The Data Object's save method actually calls doInsert for you (or doUpdate if the object is not new and must be updated).

For example, you can use AuthorPeer.doInsert as an alternative method to insert a new row in the author table. The following is from the sample application:

Author stevens = new Author();
stevens.setFirstName("W.");
stevens.setLastName("Stevens");
AuthorPeer.doInsert(stevens);

It should also be noted for completeness that doInsert can be passed a Criteria object (discussed in the next section) instead of a Data Object (see the Javadoc for details). However, the most common method for the insertion of rows in a table is via the save method of the Data Object rather than directly using the Peer's doInsert method.

Inserting a row in a table that contains a foreign key is also simple. As a convenience, Torque creates a mutator for the specific Data Object class that represents the foreign-key in the object model. The name of this method is setTable where Table is the name of the foreign-key's table (as defined in the database schema). Upon calling this method with a reference to the appropriate Data Object, Torque will automatically extract and insert the foreign-key for you.

For example, the book table (as defined in the database schema) contains two foreign-keys: author_id and publisher_id. To insert a row in this table, follow the same procedure as above, but instead of explicitly setting the foreign-keys (via setAuthorId and setPublisherId), use setAuthor and setPublisher and pass references to an Author and Publisher Data Object. Both methods are illustrated in the following code which builds upon the earlier objects that were created:

/*
 * Using the convenience methods to handle
 * the foreign keys.
 */
Book effective = new Book();
effective.setTitle("Effective Java");
effective.setISBN("0-618-12902-2");
effective.setPublisher(addison);
effective.setAuthor(bloch);
effective.save();

/*
 * Inserting the foreign-keys manually.
 */
Book tcpip = new Book();
tcpip.setTitle("TCP/IP Illustrated, Volume 1");
tcpip.setISBN("0-201-63346-9");
tcpip.setPublisherId(addison.getPublisherId());
tcpip.setAuthorId(stevens.getAuthorId());
tcpip.save();

As you can see, inserting rows into your database is very easy to do with your Torque object model.

Selecting Rows

Selecting rows from your database is just as easy as inserting rows. The Peer class associated with a table defines a static method called doSelect which is used to pull data out of the table. The argument to doSelect is a Criteria object. It is this object that specifies the criteria to be used when selecting data from the database. As a result of the query, doSelect returns a List of Data Objects representing the rows of data selected. To use these Data Objects in your application, you must cast them to the appropriate type in your object model.

For example, to select all of the rows from the book table that were inserted in the previous section, you must first create a Criteria object. Because we want to select everything from the table, no criteria will be specified (i.e. no WHERE clause in the underlying SELECT statement). To perform the query, the empty Criteria object is passed to BookPeer.doSelect, as illustrated below:

Criteria crit = new Criteria();
List books = BookPeer.doSelect(crit);

The results are stored in a List which can then be iterated over to access the individual Book objects retrieved from the table. The following code prints the Book to standard output (a better approach is presented later):

for (Iterator i = book.iterator(); i.hasNext();)
{
    Book book = (Book) i.next();
    System.out.println("Title: " + book.getTitle() + "\n");
    System.out.println("ISBN:  " + book.getISBN() + "\n");
    System.out.println("Publisher: " +
            book.getPublisher().getName() + "\n");
    System.out.println("Author: " +
            book.getAuthor().getLastName() + ", " +
            book.getAuthor().getFirstName() + "\n");
}

In the above example, you may have noticed that by calling getAuthor and getPublisher, the object model automatically retrieved the Author and Publisher Data Objects for you. This results in an additional behind-the-scenes SQL query for each table. Although getAuthor is called twice, only a single SQL query occurs because all of the Author columns are selected in behind-the-scenes query.

The Gory Details (not for the faint)
Even still, this is not the most efficient method to query and populate Data Objects for an entire table with foreign-keys (one query for the table, then two additional queries for each row). A single query using a join would be much more efficient. As a convenience, Torque generates the following protected methods in the BasePeer classes whose tables contain foreign-keys: doSelectJoinTable where Table is the name of the foreign-key table. This method efficiently queries the database (using a single join query) and automatically populates all of the Data Objects. This eliminates the additional query that is issued when retrieving the foreign-key Data Object. For example, doSelectJoinAuthor and doSelectJoinPublisher were generated in the BaseBookPeer class that BookPeer extends. As a reminder, to use these convenience methods, you must provide public members to BookPeer for clients because they are protected in BaseBookPeer. Unfortunately, Torque does not generate a doSelectJoinAll or doSelectJoinAuthorPublisher method. Those are left to the reader as an exercise to implement in the BookPeer class.

To select a specific Book from the table, create a Criteria object (or just reuse the previous one) and use the add method to specify some criteria. Specifying criteria is simply a matter of choosing a column (defined as static constants in your Peer class) and some value you want to match. Thus, selecting a book with the following ISBN, ‘0-618-12902-2’, is as simple as:

Criteria crit = new Criteria();
crit.add(BookPeer.ISBN, "0-618-12902-2");
List books = BookPeer.doSelect(crit);

This section has only skimmed the surface of Criteria objects. Criteria can be used to specify very simple to very complex queries. For a much more in-depth discussion of Criteria, please refer to the reading from the database Reference.

Updating Rows

Updating a row in a table is only a matter of changing one or more properties of the Data Object that represents the row by invoking one or more mutators and then calling its save method. When a mutator is called, the Data Object sets an internal flag to indicate that its been modified. This flag is checked when save is invoked to determine if the Peer's doInsert or doUpdate is called to perform the database operation.

For example, changing the author of the ‘Effective Java’ book created earlier is as simple as:

effective.setAuthor(stevens);
effective.save();

Alternatively, instead of calling the Data Object's save method, the Peer's doUpdate method may be called directly with a Data Object that has been modified as the argument. This is illustrated in the following fragment of code that changes the author of the ‘TCP/IP Illustrated’ book:

tcpip.setAuthor(bloch);
BookPeer.doUpdate(tcpip);

Again, for completeness, doUpdate could have been passed a Criteria object to update a row (see the Javadoc for details). However, the most common method to update rows in a table is via the Data Object's save method rather than directly using the Peer's doUpdate method.

Deleting Rows

Deleting rows from a table is easy as well. The Peer class defines a static method doDelete which can be used for this purpose. Similar to the other Peer methods, doDelete may be passed a Criteria object or a Data Object to specify which row or rows to delete. It should be noted that there is no corresponding method in the Data Object to delete a row.

For example, the following code deletes all of the rows from the three tables that were inserted during the course of this tutorial using both forms of doDelete. First, the books are deleted by specifying Criteria, then the authors and publishers are deleted by passing the Data Objects directly to doDelete.

crit = new Criteria();
crit.add(BookPeer.ISBN, "0-618-12902-2");
BookPeer.doDelete(crit);

crit = new Criteria();
crit.add(BookPeer.ISBN, "0-201-63346-9");
crit.add(BookPeer.TITLE, "TCP/IP Illustrated, Volume 1");
BookPeer.doDelete(crit);

AuthorPeer.doDelete(bloch);
AuthorPeer.doDelete(stevens);
PublisherPeer.doDelete(addison);

Note: Deleting a row from a table that contains foreign-keys does not automatically delete the foreign-keys from their tables. If you want to delete the foreign-keys, you must do so explicitly as shown in the above example. I.e., deleting the books from the book table does not automatically delete the corresponding rows in the author and publisher tables.

The Gory Details (not for the faint)
It should also be noted that doDelete does not construct its WHERE clause in a similar manner as the doSelect method. doDelete processes Criteria in a more primitive fashion. Specifically, Criteria assembled using the and and or methods (not covered in this tutorial) are effectively ignored. In addition, passing an empty Criteria to doDelete will not delete all of the rows from a table. In summary, you cannot assume that a Criteria object which successfully selects rows from a table via doSelect will delete those rows if passed to doDelete. In the future, doDelete may be modified to be consistent in the handling of Criteria objects.

Adding Functionality to the Object Model

This section will provide examples of adding functionality to both the Peer and Data Object classes. As you may recall, Torque generated four classes for each table defined in the database schema. Two of these classes (the Base Data Object and Base Peer class) contain Torque-generated logic while the other two are empty subclasses that you can use to include business logic. By now, you should have a decent understanding of the type of logic that might be added to these classes. Keep in mind, Torque will overwrite any changes that are inadvertently added to the Base classes if you regenerate your object model; however, it will not overwrite changes in the non-Base classes.

The first change that we'll make to our object model is to provide our Data Objects with adequate toString methods. Theses methods can then be used to print the Data Objects without adding unnecessary code to the core of the application. The following are the modified Book, Author, and Publisher classes, which are located in a directory hierarchy matching that of the targetPackage you specified in your Torque build.properties:

// Book.java
import org.apache.torque.TorqueException;

public  class Book
    extends com.kazmier.om.BaseBook
{
    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        try
        {
            sb.append("Title:     " + getTitle() + "\n");
            sb.append("ISBN:      " + getISBN() + "\n");
            sb.append("Publisher: " + getPublisher() + "\n");
            sb.append("Author:    " + getAuthor() + "\n");
        }
        catch (TorqueException ignored)
        {
        }
        return sb.toString();
    }
}

// Author.java
public  class Author
    extends com.kazmier.om.BaseAuthor
{
    public String toString()
    {
        return getLastName() + ", " + getFirstName();
    }
}

// Publisher.java
public  class Publisher
    extends com.kazmier.om.BasePublisher
{
    public String toString()
    {
      return getName();
    }
}

The next change that we'll make is to the Peer classes. For convenience (and based on the suggestion in the Reading from the Database Reference) we'll add doSelectAll methods which will return a List of all the Data Objects in a table. The following are the modified BookPeer, AuthorPeer, and PublisherPeer classes which are located in the same directory as the Data Objects:

// BookPeer.java
import java.util.List;
import org.apache.torque.TorqueException;
import org.apache.torque.util.Criteria;

public class BookPeer
    extends com.kazmier.om.BaseBookPeer
{
    public static List doSelectAll() throws TorqueException
    {
        Criteria crit = new Criteria();
        return doSelect(crit);
    }
}

// AuthorPeer.java
import java.util.List;
import org.apache.torque.TorqueException;
import org.apache.torque.util.Criteria;

public class AuthorPeer
    extends com.kazmier.om.BaseAuthorPeer
{
    public static List doSelectAll() throws TorqueException
    {
        Criteria crit = new Criteria();
        return doSelect(crit);
    }
}

// PublisherPeer.java
import java.util.List;
import org.apache.torque.TorqueException;
import org.apache.torque.util.Criteria;

public class PublisherPeer
  extends com.kazmier.om.BasePublisherPeer
{
    public static List doSelectAll() throws TorqueException
    {
        Criteria crit = new Criteria();
        return doSelect(crit);
    }
}

In order to execute the full application presented at the end of this tutorial, you must make the above changes to your object model. After you have made the changes, proceed to the next section.

Full Application

The following is the sample bookstore application in its entirety. It should look very familiar if you've been following this tutorial. In fact, its almost identical with the exception that it utilizes the new functionality that was added to the object model in the previous section. Note in particular the all-important initialization of Torque using the torque.properties file we created earlier.

package com.kazmier;

import java.util.*;
import com.kazmier.om.*;
import org.apache.torque.Torque;
import org.apache.torque.util.Criteria;

public class Bookstore
{
    public static void main(String[] args)
    {
        try
        {
            /*
             * Initializing Torque
             */
            Torque.init("torque.properties");

            /*
             * Creating new objects. These will be inserted into your database
             * automatically when the save method is called.
             */
            Publisher addison = new Publisher();
            addison.setName("Addison Wesley Professional");
            addison.save();

            Author bloch = new Author();
            bloch.setFirstName("Joshua");
            bloch.setLastName("Bloch");
            bloch.save();

            /*
             * An alternative method to inserting rows in your database.
             */
            Author stevens = new Author();
            stevens.setFirstName("W.");
            stevens.setLastName("Stevens");
            AuthorPeer.doInsert(stevens);

            /*
             * Using the convenience methods to handle the foreign keys.
             */
            Book effective = new Book();
            effective.setTitle("Effective Java");
            effective.setISBN("0-618-12902-2");
            effective.setPublisher(addison);
            effective.setAuthor(bloch);
            effective.save();

            /*
             * Inserting the foreign-keys manually.
             */
            Book tcpip = new Book();
            tcpip.setTitle("TCP/IP Illustrated, Volume 1");
            tcpip.setISBN("0-201-63346-9");
            tcpip.setPublisherId(addison.getPublisherId());
            tcpip.setAuthorId(stevens.getAuthorId());
            tcpip.save();

            /*
             * Selecting all books from the database and printing the results to
             * stdout using our helper method defined in BookPeer (doSelectAll).
             */
            System.out.println("Full booklist:\n");
            List booklist = BookPeer.doSelectAll();
            printBooklist(booklist);

            /*
             * Selecting specific objects. Just search for objects that match
             * this criteria (and print to stdout).
             */
            System.out.println("Booklist (specific ISBN):\n");
            Criteria crit = new Criteria();
            crit.add(BookPeer.ISBN, "0-201-63346-9");
            booklist = BookPeer.doSelect(crit);
            printBooklist(booklist);

            /*
             * Updating data. These lines will swap the authors of the two
             * books. The booklist is printed to stdout to verify the results.
             */
            effective.setAuthor(stevens);
            effective.save();

            tcpip.setAuthor(bloch);
            BookPeer.doUpdate(tcpip);

            System.out.println("Booklist (authors swapped):\n");
            booklist = BookPeer.doSelectAll();
            printBooklist(booklist);

            /*
             * Deleting data. These lines will delete the data that matches the
             * specified criteria.
             */
            crit = new Criteria();
            crit.add(BookPeer.ISBN, "0-618-12902-2");
            BookPeer.doDelete(crit);

            crit = new Criteria();
            crit.add(BookPeer.ISBN, "0-201-63346-9");
            crit.add(BookPeer.TITLE, "TCP/IP Illustrated, Volume 1");
            BookPeer.doDelete(crit);

            /*
             * Deleting data by passing Data Objects instead of specifying
             * criteria.
             */
            AuthorPeer.doDelete(bloch);
            AuthorPeer.doDelete(stevens);
            PublisherPeer.doDelete(addison);

            System.out.println("Booklist (should be empty):\n");
            booklist = BookPeer.doSelectAll();
            printBooklist(booklist);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    /*
     * Helper method to print a booklist to standard out.
     */
    private static void printBooklist(List booklist)
    {
        for (Iterator i = booklist.iterator(); i.hasNext();)
        {
            Book book = (Book) i.next();
            System.out.println(book);
        }
    }
}
  

Save this code in the src/java directory hierarchy with a filename of Bookstore.java. The above example must be placed in src/java/com/kazmier directory because of its package definition. Your application might go elsewhere depending on the package that you've selected.

Where to next

Now you have finished writing your sample application. The next step shows you how to compile and run the sample application.

Next we will look Compiling and Running the Sample Application.

User Comments

User comments for this step