http://www.developer.com/

Back to article

Installing and Using the Torque Object Mapper


September 5, 2002

Larry Wall (father of Perl) is fond of saying that one of the virtues of a good programmer is laziness. By this he means that the best programmers never do work that they can avoid by using someone else's tools.

In Java, there has always been a lot of fairly mindless work that keeps developers from getting to the real job of implementing business logic. One of them, implementing the boilerplate get and set accessors for beans, has been largely eliminated by technologies such as IDEs and now the new DynaBeans framework.

One nettlesome chore still takes up a large amount of development time, however. That is implementing the persistence layer in an application. Anyone who has spent time with a database-enabled project knows the pain that this entails, not so much because it is intellectually difficult, but because it requires a lot of code to handle retrieving, creating, deleting, and updating data.

The Apache Jakarta Torque project is a spin-off of the Turbine application framework; it allows a developer to create one XML file, and have both a persistence layer and database schema automatically generated for them. By using Torque, it is possible to database-enable a simple application in a matter of hours.

To see how Torque works, we'll look at a sample application with a dose of Torque magic. Let's assume that we have a group of database tables we're using to track baseball pitchers, described in conventional SQL (of the MySQL dialect) here:

Here's Table 1:

CREATE TABLE PITCHER
(
  PITCHER_ID INTEGER NOT NULL AUTO_INCREMENT,
  LAST_NAME VARCHAR (30) NOT NULL,
  FIRST_NAME VARCHAR (30) NOT NULL,
  TEAM_ID INTEGER NOT NULL,
  PRIMARY KEY(PITCHER_ID),
  FOREIGN KEY (TEAM_ID) REFERENCES TEAM (TEAM_ID)
);

Here's Table 2:

CREATE TABLE TEAM
(
  TEAM_ID INTEGER NOT NULL,
  TEAM_NAME VARCHAR (30) NOT NULL,
  PRIMARY KEY(TEAM_ID)
);

And here's Table 3:

CREATE TABLE GAME
(
  PITCHER_ID INTEGER NOT NULL,
  PLAYING_FOR INTEGER NOT NULL,
  AGAINST_TEAM INTEGER NOT NULL,
  OUTS_RECORDED INTEGER NOT NULL,
  HITS INTEGER,
  RUNS INTEGER,
  WALKS INTEGER,
  STRIKE_OUTS INTEGER,
  FOREIGN KEY (PITCHER_ID) REFERENCES PITCHER (PITCHER_ID),
  FOREIGN KEY (PLAYING_FOR) REFERENCES TEAM (TEAM_ID),
  FOREIGN KEY (AGAINST_TEAM) REFERENCES TEAM (TEAM_ID)
);

You can use this schema to record data about the performance of pitchers as well as teams. Each pitcher belongs to a team, each team has a cadre of pitchers, and each pitcher has a record of how they have performed in each inning pitched.

In a conventional approach, you would need to write methods to read, write, update, and delete each of these tables, as well as creating the objects to model them. By using Torque, all this is taken care of.

To install Torque, you download the Torque binary. As of this writing, the nightly build is the most stable, and its ZIP file can be found at http://jakarta.apache.org/builds/jakarta-turbine/alpha/. When you unpack the zip, you'll find the following files in the newly created torque directory.

Torque.properties
build-torque.xml
build.properties
build.xml
database/
docs/
lib/
master/
schema/
src/
templates/
velocity.log

The first step is to configure the build.properties file. Only the top of the file needs to be edited. Here are the critical items, with the values being used for this example:

project = pitchers

The name of the project, used to find the database definitions in the schema directory:

database = mysql

The type of database being used, one of: db2400, hypersonic, mssql, mysql, oracle, postgresql, sapdb, and sybase. You also can write your own database adaptors, a discussion outside the scope of this article.

targetPackage=pitchers.torque

The package in which the generated classes that define the object mapping will be created. I find it's helpful to use "torque" in the package path to make them stand out and remind me that they're generated by Torque.

createDatabaseUrl = jdbc:mysql://127.0.0.1/
databaseUrl = jdbc:mysql://127.0.0.1/pitchers
databaseDriver = org.gjt.mm.mysql.Driver
databaseUser = pedro
databasePassword = ace
databaseHost = 127.0.0.1

The information needed to connect to the database is similar to the information used when setting up a connection pool. Note that you need to provide the databaseDriver and put it in the Torque lib directory for things to work correctly.

Now you're ready to create your database schema. The schema file is in the schema subdirectory, and is called project-schema.xml. As shipped, it has demonstration data in it, which we'll replace with our data.

<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
<!DOCTYPE database SYSTEM 
          "http://jakarta.apache.org/turbine/dtd/database.dtd">

This is just a normal XML header.

<database name="PITCHERS" defaultIdMethod="native">

Next, we tell Torque that we're defining a database called "PITCHERS". The defaultIdMethod parameter tells Torque how to handle integer primary key columns. If "native" is selected, Torque will allow the underlying database to use its own native implementation. If "idBroker" is specified, Torque will use its own vendor-neutral implementation which will work even for databases that don't support auto-incrementing primary keys. The downside to using the ID Broker is that it forces Torque to do a lot of acrobatics to keep the ids in sync between multiple threads or processes (or even servers), something that the native database implementations can usually handle better.

<table name="PITCHER">
<column name="PITCHER_ID" required="true" autoIncrement="true"
        primaryKey="true" type="INTEGER/>
<column name="LAST_NAME" required="true" size="30" type="VARCHAR"/>
<column name="FIRST_NAME" required="true" size="30" type="VARCHAR"/>
<column name="TEAM_ID" required="true" type="INTEGER"/>
<foreign-key foreignTable="TEAM">
<reference local="TEAM_ID" foreign="TEAM_ID"/>
</foreign-key>
</table>

Each table definition begins by specifying the name of the table. Next, each column is defined. Note that PITCHER_ID is flagged as both autoIncrement and primaryKey. This means that when a new row is created, the database will automatically increment this value. It also means that the developer can't write out a new value with this column specified; it will always be generated automatically.

After the columns are defined, any foreign keys are listed. Although this may not be required from the perspective of the database (MySQL doesn't support foreign keys, for example), it is also a flag to Torque to create relationships between the two objects, as you will see later.

<table name="TEAM" idMethod="none">
<column name="TEAM_ID" required="true" primaryKey="true"
        type="INTEGER"/>
<column name="TEAM_NAME" required="true" size="30" type="VARCHAR"/>
</table>

In the case of the TEAM table, we need to specify idMethod="none" because the teams are loaded in by hand, and the team ID numbers are explicitly specified. The "none" value tells Torque that although there are columns that are primary keys, Torque should not attempt to manage them.

<table name="GAME" idMethod="none">
<column name="PITCHER_ID" required="true" type="INTEGER"/>
<column name="PLAYING_FOR" required="true" type="INTEGER"/>
<column name="AGAINST_TEAM" required="true" type="INTEGER"/>
<column name="OUTS_RECORDED" required="true" type="INTEGER"/>
<column name="HITS" type="INTEGER"/>
<column name="RUNS" type="INTEGER"/>
<column name="WALKS" type="INTEGER"/>
<column name="STRIKE_OUTS" type="INTEGER"/>
<foreign-key foreignTable="PITCHER">
<reference local="PITCHER_ID" foreign="PITCHER_ID"/>
</foreign-key>
<foreign-key foreignTable="TEAM">
<reference local="PLAYING_FOR" foreign="TEAM_ID"/>
</foreign-key>
<foreign-key foreignTable="TEAM">
<reference local="AGAINST_TEAM" foreign="TEAM_ID"/>
</foreign-key>
</table>

Finally, the table that holds the actual game statistics. In this case, we still have to specify idMethod="none", this time because there are no primary keys at all, and Torque gets confused if you don't explicitly tell it not to manage such a table. This table also has three separate foreign keys.

Once the schema is written, you use Ant to have Torque generate both the Java and the SQL files for the application.

D:\gamelan\torque>ant -f build-torque.xml
Buildfile: build-torque.xml

main:

project-sql:
[echo] +------------------------------------------+
[echo] |                                          |
[echo] | Generating SQL for YOUR Turbine project! |
[echo] | Woo hoo!                                 |
[echo] |                                          |
[echo] +------------------------------------------+
[torque-sql] Using contextProperties file:
             D:\gamelan\torque\build.properties
[torque-sql] Generating to file
 D:\gamelan\torque\src\sql\report.pitchers.sql.generation
[torque-sql] Resolver: used database.dtd from
 org.apache.torque.engine.database.transform package
[torque-sql] Resolver: used database.dtd from
 org.apache.torque.engine.database.transform package
[torque-sql] Resolver: used database.dtd from
 org.apache.torque.engine.database.transform package
[torque-sql] Resolver: used database.dtd from
 org.apache.torque.engine.database.transform package

project-om:
[echo] +------------------------------------------+
[echo] |                                          |
[echo] | Generating Peer-based Object Model for   |
[echo] | YOUR Turbine project! Woo hoo!           |
[echo] |                                          |
[echo] +------------------------------------------+
[torque-om] Using contextProperties file:
 D:\gamelan\torque\build.properties
[torque-om] Generating to file
 D:\gamelan\torque\src\java\report.pitchers.om.generation
[torque-om] Resolver: used database.dtd from 
 org.apache.torque.engine.database.transform package
[torque-om] Resolver: used database.dtd from 
 org.apache.torque.engine.database.transform package

BUILD SUCCESSFUL

Total time: 2 seconds

You have to enjoy any build process that involves the word Woo hoo!

With the schema written, you need to create the user in the database, and give it privileges to create the database and tables. Assuming that MySQL is already installed, you'd do it like this:

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8 to server version: 3.23.45-nt

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> grant all privileges on *.* to
       pedro@localhost identified by 'ace';
Query OK, 0 rows affected (0.00 sec)

mysql> quit

Now you can let Torque create the database and populate the tables, using ant again.

D:\gamelan\torque>ant project-create-db
Buildfile: build.xml

project-create-db:
[torque-create-db] Generating to file
 D:\gamelan\torque\src\sql\create-db.sql
[torque-create-db] Resolver: used database.dtd from 
 org.apache.torque.engine.database.transform package
[torque-create-db] Resolver: used database.dtd from 
 org.apache.torque.engine.database.transform package
[torque-create-db] Resolver: used database.dtd from 
 org.apache.torque.engine.database.transform package
[torque-create-db] Resolver: used database.dtd from 
 org.apache.torque.engine.database.transform package
[sql] Executing file: D:\gamelan\torque\src\sql\create-db.sql
[sql] 2 of 2 SQL statements executed successfully

BUILD SUCCESSFUL

Total time: 1 second
D:\gamelan\torque>ant project-insert-sql
Buildfile: build.xml

project-insert-sql:
[torque-insert-sql] Our new url -> jdbc:mysql://127.0.0.1/pitchers
[torque-insert-sql] Executing file:
                    D:\gamelan\torque\src\sql\id-table-schema.sql
[torque-insert-sql] 6 of 6 SQL statements executed successfully
[torque-insert-sql] Our new url -> jdbc:mysql://127.0.0.1/pitchers
[torque-insert-sql] Executing file:
                    D:\gamelan\torque\src\sql\project-schema.sql

[torque-insert-sql] 18 of 18 SQL statements executed successfully

BUILD SUCCESSFUL

Total time: 0 seconds

With the database built and populated, we can talk about what's happening on the Java side. If you look in the src subdirectory, you'll see two directories inside it. One, "sql", contains the automatically generated SQL used to create the database. The other, "java", is the head of a source tree that contains the object mapping implementation. Here's the layout:

src
├───java
│    └───pitchers
│           └───torque
│                  └───map
└───sql

All the files that we care about live in the torque directory. For each table, there are four classes defined in this directory. For example, the TEAM table has four corresponding classes: BaseTeam, BaseTeamPeer, Team, and TeamPeer.

The Base files contain the actual implementation details of the table, and are re-created each time you do an ant build. The Peer class is something like a static method; it's used to deal with the table as a whole. The non-Peer class is the implementation of a row from this table. This will become clearer when you see them in use.

The non-Base classes start out empty. For example, here is TeamPeer.java:

package pitchers.torque;

import java.util.*;
import com.workingdogs.village.*;
import org.apache.torque.map.*;
import org.apache.torque.pool.DBConnection;

// Local classes
import pitchers.torque.map.*;

/** 
* The skeleton for this class was autogenerated by Torque on:
*
* [Thu Aug 01 23:35:05 EDT 2002]
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
public class TeamPeer
extends pitchers.torque.BaseTeamPeer
{
}

As you can see, all it does is extend the Base class. The important thing to know about these two files is that they are not overwritten on rebuilds, so you can put extensions and business logic in them.

The first extension you could add is a helper function for the Team class to allow you to look up a team by name (remembering that the unique identifier for teams is not the team name but an integer sequence, so that teams can easier change names if they are bought or sold). Here's the source for Team.java:

package pitchers.torque;

import org.apache.torque.util.*;
import org.apache.torque.om.Persistent;
import java.util.List;

/** 
* The skeleton for this class was autogenerated by Torque on:
*
* [Thu Aug 01 23:35:05 EDT 2002]
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
public class Team 
extends pitchers.torque.BaseTeam
implements Persistent
{

  public static Team findTeamByName(String name) throws Exception{
    Criteria c = new Criteria();
    c.add(TeamPeer.TEAM_NAME, name);
    List team = TeamPeer.doSelect(c);
    if (team.size() != 1) {
      return null;
    }
    return (Team) team.get(0);
  }
}

This code illustrates several features of Torque. To begin with, it uses the Criteria class. Criteria are equivalent to the WHERE clause in an SQL statement. Each Peer class has static string variables that correspond to the columns of that table. So in this case, after creating a new Criteria, you can add the condition that the team name must equal the name passed in to the method (if you only pass two arguments to the "add" method, the EQUAL test is assembled).

Next, TeamPeer.doSelect is called, with the Criteria as its argument. The doSelect method returns a List with zero or more elements, depending on how many rows in the database matched. Since there should only ever be a single team in the database with a given name, the code returns null if either zero or more than one row is returned; otherwise, it returns the team.

The Team class is basically a bean with getters and setters for all the columns, as well as knowing how to save itself or create new records.

The only other class that you need to extend is the Pitcher class, to add some business logic and helper functions.

package pitchers.torque;
import java.util.Iterator;
import java.util.List;
import org.apache.torque.util.*;
import pitchers.torque.*;


import org.apache.torque.om.Persistent;

/** 
* The skeleton for this class was autogenerated by Torque on:
*
* [Thu Aug 01 23:35:05 EDT 2002]
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
public class Pitcher
extends pitchers.torque.BasePitcher
implements Persistent
{

  public static Pitcher findPitcherByName
                (String first, String last)
    throws Exception {
    Criteria c = new Criteria();
    c.add(PitcherPeer.FIRST_NAME, first);
    c.and(PitcherPeer.LAST_NAME, last);
    List pitchers = PitcherPeer.doSelect(c);
    if (pitchers.size() != 1) {
      return null;
    }
    return (Pitcher) pitchers.get(0);
  }

  public double computeERA() throws Exception {
    Iterator c = getAllGames().iterator();
    int outs = 0;
    int runs = 0;

    while (c.hasNext()) {
      Game g = (Game) c.next();
      outs += g.getOutsRecorded();
      runs += g.getRuns();
    }

    // ERA is (earned runs * 9) / innings pitched
    // 3 outs per inning, so it's (earned runs * 9) / (outs / 3)
    // Or, (earned runs * 27) / outs
    return (runs * 27.0) / outs;
  }

  public double computeERAAgainst(Team t) throws Exception {
    Iterator c = getGamesAgainst(t).iterator();
    int outs = 0;
    int runs = 0;

    while (c.hasNext()) {
      Game g = (Game) c.next();
      outs += g.getOutsRecorded();
      runs += g.getRuns();
    }

    // ERA is (earned runs * 9) / innings pitched
    // 3 outs per inning, so it's (earned runs * 9) / (outs / 3)
    // Or, (earned runs * 27) / outs
    return (runs * 27.0) / outs;
  }

  public List getAllGames() throws Exception {
    Criteria c = new Criteria();
    c.add(GamePeer.PITCHER_ID, this.getPitcherId());
    return GamePeer.doSelect(c);
  }

  public List getGamesAgainst(Team t) throws Exception {
    Criteria c = new Criteria();
    c.add(GamePeer.PITCHER_ID, this.getPitcherId());
    c.and(GamePeer.AGAINST_TEAM, t.getTeamId());
    return GamePeer.doSelect(c);
  }
}

The findPitcherByName method is essentially the same as the findTeamByName method you've already seen, except that it shows an example of a compound Criteria. After adding the first condition using the Criteria.add method, all subsequent conditions should either use Criteria.and or Criteria.or, to specify how the WHERE clause should be constructed.

The getAllGames method demonstrates that one class in Torque can implement a query against another. In this case, the Pitcher's Id (which is of type NumberKey) is retrieved, and then placed as a condition on the Criteria. That Criteria then is used to look up matching rows in the GAME table. Basically, whatever Peer is used for the doSelect is the table that the query will be run against, and the type of object that will be returned. The getGamesAgainst method is the same as the getAllGames method, but with an additional condition checking for the opposing team.

The remainder of the code implements ERA computation, which is a relatively straightforward matter. With that in place, you can write your main class:

package pitchers;

import pitchers.torque.*;
import org.apache.torque.om.*;
import org.apache.torque.util.*;
import org.apache.torque.Torque;
import java.util.List;
import java.text.DecimalFormat;

public class PitcherStats {

  private static DecimalFormat df = new DecimalFormat("0.00");

  private static String[]
          teamnames = {"RedSox", "Yankees", "DevilRays",
                       "WhiteSox", "Cubs", "Dodgers", "Marlins",
                       "Angels", "Orioles", "Royals", "Athletics",
                       "BlueJays", "Tigers", "Rockies", "Braves"
                      };

  public static void main(String args[]) {
    if (args.length == 0) {
      System.out.println
             ("Usage: pitcher [add|init|game|era|eravs|clear]");
      return;
    }

    String command = args[0];

    try {
      Torque.init
             ("d:/gamelan/torque/bin/classes/Torque.properties");
      if (command.equals("init")) {
        for (int i = 0; i < teamnames.length; i++) {
          Team t = new Team();
          t.setTeamId(new NumberKey(i + 1));
          t.setTeamName(teamnames[i]);
          t.save();
        }
      }

      if (command.equals("clear")) {
        Criteria c = new Criteria();
        c.add(TeamPeer.TEAM_ID, -1, Criteria.GREATER_EQUAL);
        TeamPeer.doDelete(c);
      }

      if (command.equals("era")) {
        if (args.length != 3) {
          System.out.println("Usage: pitcher era last first");
          return;
        }
        Pitcher p = Pitcher.findPitcherByName(args[1], args[2]);
        if (p == null) {
          System.out.println("No such pitcher: " +
                              args[1] + " " + args[2]);
          return;
        }
        System.out.println("The ERA for " + args[1] + 
                            " " + args[2] + " is " + 
                            df.format(p.computeERA()));
      }

      if (command.equals("eravs")) {
        if (args.length != 4) {
          System.out.println
                 ("Usage: pitcher eravs last first team");
          return;
        }
        Pitcher p = Pitcher.findPitcherByName(args[1], args[2]);
        if (p == null) {
          System.out.println("No such pitcher: " +
                              args[1] + " " + args[2]);
          return;
        }
        Team t = Team.findTeamByName(args[3]);
        if (t == null) {
          System.out.println("No such team: " + args[3]);
          return;
        }
        System.out.println("The ERA for " + args[1] + 
                           " " + args[2] + " against the " +
                           args[3] + " is " +
                           df.format(p.computeERAAgainst(t)));
        }

        if (command.equals("add")) {
          if (args.length != 4) {
          System.out.println
                 ("Usage: pitcher add last first team");
          return;
        }

        Team t = Team.findTeamByName(args[3]);
          if (t == null) {
          System.out.println("No such team: " + args[3]);
          return;
        }
        Pitcher p = new Pitcher();
        p.setTeam(t);
        p.setFirstName(args[1]);
        p.setLastName(args[2]);
        p.save();
        System.out.println("Added player!");
        return;
      }
      if (command.equals("game")) {
        if (args.length != 9) {
          System.out.println
                 ("Usage: pitcher game first last team/opponent
                   hits runs outs walks Ks");
          return;
        }
        int divide = args[3].indexOf("/");
        if (divide == -1) {
          System.out.println("Format is pitcherteam/opposing");
          return;
        }
        String pitcherteam = args[3].substring(0, divide);
        String oppteam = args[3].substring(divide + 1);
        Pitcher p = Pitcher.findPitcherByName(args[1], args[2]);
        if (p == null) {
          System.out.println("No such pitcher: " +
                              args[1] + " " + args[2]);
          return;
        }

        Team team = Team.findTeamByName(pitcherteam);
        if (team == null) {
          System.out.println("No such team: " + pitcherteam);
          return;
        }

        Team opponent = Team.findTeamByName(oppteam);
        if (opponent == null) {
          System.out.println("No such team: " + oppteam);
          return;
        }

        int hits = Integer.parseInt(args[4]);
        int runs = Integer.parseInt(args[5]);
        int outs = Integer.parseInt(args[6]);
        int walks = Integer.parseInt(args[7]);
        int ks = Integer.parseInt(args[8]);

        Game i = new Game();
        i.setPitcher(p);
        i.setTeamRelatedByPlayingFor(team);
        i.setTeamRelatedByAgainstTeam(opponent);
        i.setHits(hits);
        i.setRuns(runs);
        i.setOutsRecorded(outs);
        i.setStrikeOuts(ks);
        i.setWalks(walks);
        i.save();
        System.out.println("Added Game!");
        return;
        }
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
}

Most of this file is merely creating a command-line user interface for the business logic already implemented, but there are a few things to note. First, because this is a stand-alone application, it needs to call Torque.init explicitly, passing the location of the properties file. In a Web application, this could be handled by a servlet that's called during startup.

The "clear" function shows two features. First, if a Criteria condition is created with a third argument, that argument can be used to specify a test such as ">=" or "LIKE". In the case of the "clear" function, it is used to specify all records by specify all IDs > -1. It also shows the doDelete method on the Peer, which does what you'd expect, deleting all rows that match the Critieria.

The "game" function shows one last feature. If there is a one-to-one relationship between two tables by a foreign key, a get and set method are added to that table. For example, Game has a getPitcher and setPitcher method because Game joins to Pitcher by PITCHER_ID.

If one table has several one-to-one foreign-key relations to another table, Torque creates a special accessor. This takes the form of "getXRelatedByY, where X is the foreign table, and Y is the row of this table that is the key." It also works when creating rows. For example, the Game class has two foreign keys to Team, which can be accessed using getTeamRelatedByPlayingFor and getTeamRelatedByAgainstTeam (and the corresponding set methods.)

After compilation, you can take the code for a test drive:

D:\gamelan>.\pitcher add James Turner RedSox
Added player!

D:\gamelan>.\pitcher game James Turner
                     RedSox/Yankees 0 0 27 0 27
Added Game!

D:\gamelan>.\pitcher game James Turner RedSox/DevilRays 2 1 9 0 5
Added Game!

D:\gamelan>.\pitcher era James Turner
The ERA for James Turner is 0.75

D:\gamelan>.\pitcher eravs James Turner Yankees
The ERA for James Turner against the Yankees is 0.00

D:\gamelan>.\pitcher eravs James Turner DevilRays
The ERA for James Turner against the DevilRays is 3.00

D:\gamelan>.\pitcher era Derek Lowe
The ERA for Derek Lowe is 2.23

D:\gamelan>.\pitcher eravs Derek Lowe DevilRays
The ERA for Derek Lowe against the DevilRays is 1.16

The pitcher.bat file is just a wrapper on the Java call; that sets up the libraries correctly:

@echo off
set demodir=d:\gamelan
set classpath=%demodir%\torque\bin\classes;%demodir%\torque\lib\
    commons-collections.jar;%demodir%\torque\lib\
    commons-lang-0.1-dev.jar;%demodir%\torque\lib\
    jdbc2_0-stdext.jar;%demodir%\torque\lib\
    log4j-1.1.3.jar;%demodir%\torque\lib\
    stratum-1.0-b2-dev.jar;%demodir%\torque\lib\
    torque-3.0-b2.jar;%demodir%\torque\lib\
    velocity-1.3-dev.jar;%demodir%\torque\lib\
    village-1.5.3.jar;%demodir%\torque\lib\
    xercesImpl-2.0.0.jar;%demodir%\torque\lib\
    xmlParserAPIs-2.0.0.jar;%demodir%\torque\lib\
    mm.mysql-2.0.14-bin.jar;%demodir%\torque\lib\
    commons-configuration-1.0-dev.jar
java pitchers.PitcherStats %1 %2 %3 %4 %5 %6 %7 %8 %9

This tutorial just scratches the surface of what you can do with Torque, but is essentially all you need to know to develop a complete application using it. For more information or Torque, consult the Apache Web site at http://jakarta.apache.org/turbine/torque/index.html. You can find the complete sources to this application at http://www.blackbear.com/downloads/torque.jar.

James Turner is the owner and manager of Black Bear Software, LLC, which specializes in custom Java-based e-Commerce and CRM solutions delivery. He is also the author of "MySQL and JSP Web Applications: Data-Driven Programming Using Tomcat and MySQL" (ISBN: 0672323095) and is the co-author of "Struts: Kick Start" (ISBN: 0672324725), which will be published in November. He can be reached at turner@blackbear.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date