Tutorial

Introduction

This document is intended to give a quick overview on how to use XORM. It is not intended to help XORM developers learn the internals of XORM.

The document jumps right to the chase by kicking things off with several examples of using XORM before getting into the details.

More information about XORM can be found on the main page, including how to checkout the source code.

Examples

The examples below are based on the following Class Diagram.


This structure is a typical representation of a music library. An Artist or set of Artists record Songs, which may appear on multiple Albums as different Tracks.

Each of these classes is represented by a corresponding table in the database. In addition, the many-to-many relationship between Song and Artist is represented by a many-to-many join table in the database. See the model section below for information on how it maps to DB tables.

Example: Query

This example shows how to read an object from the database using XORM and then traverse its relationships. An explanation follows.

        // Package definitions and imports not shown
 1      PersistenceManagerFactory factory =
 2        XORM.newPersistenceManagerFactory("jdo.properties");
 3      PersistenceManager mgr =
 4          factory.getPersistenceManager();
 5      Query query = mgr.newQuery(Track.class,
 6                                 "song.title == v1");
 7      query.declareParameters("String v1");
 8      Collection tracks = (Collection)
 9          query.execute("A Day in the Life");
10      Iterator trackIter = tracks.iterator();
11      while (trackIter.hasNext()) {
12          Track track = (Track) trackIter.next();
13          System.out.println("Track: " +
14             track.getSong().getTitle() +
15             " appears on " +
16             track.getAlbum().getTitle() +
17             " as number " + track.getNumber());
18      }

The first step in using XORM is to get a PersistenceManager. Most of the calls you make into XORM are done using the PersistenceManager.

Lines 1-2 involve getting a PersistenceManagerFactory. As input, it takes the name of a JDO property file that specifies what database and JDO implementation to use.

Lines 3-4 acquires a PersistenceManager from the factory. Generally, you create one factory for your application and get PersistenceManagers from that factory.

Lines 5-6 use the newly acquired PersistenceManager to create a new query. The query language is specified in the JDO specification. It's asking for instances of Track, and restricting the Track returned to Tracks that are recordings of a Song, where the Song has some title. The query language refers to fields by their property name, hence Song is specified using a starting lower case letter, "song". XORM will throw a cryptic exception if you accidentally specify a property name using the wrong case which is easy to do because the property name usually matches the class name except for the case of the first letter.

Line 7 tells XORM that the "v1" we specified in the query is really a parameter and it's of type String.

Lines 8-9 cause XORM to run the query, using "A Day in the Life" as the value for "v1". The values for parameters are passed in using the order declared to XORM in the declareParameters call. The call returns all the Track objects found in a collection.

Lines 10-11 we go through the returned collection to use the Track. Note that XORM uses a lazy query mechanism, so even though we called executeQuery, XORM still hasn't actually run the query against the DB. Using the returned collection in the while loop causes XORM to finally run the query.

Line 12 extracts a Track object from the returned collection.

Lines 13-14 we use the returned Track object. First we call getSong() on the Track. If you look at the data model, every Track belongs to a Song so the Track interface has a getSong() method on it. Calling getSong().getTitle() causes XORM to read the Performance from the DB, allowing you to seamlessly traverse the object hierarchy. XORM, being lazy, doesn't actually fetch the Performance until the getTitle() call.

Lines 15-17. All Tracks also have a reference to an Album, and have a track number attribute. These lines traverse the Album relationship (again, XORM will load in the Album object on demand) to access the appropriate properties.

Example: Transaction

This example shows how to use XORM to modify a DB object and create a new DB object. XORM is transaction based; so all modifications of objects MUST be performed inside of a Transaction. Queries and traversing objects can be done outside the scope of a Transaction.

       // Uses PersistenceManager and track objects from Query Example
1      Transaction t = mgr.currentTransaction();
2      t.begin();
3      track.getSong().setTitle("Day in the Life");
4      Song song = (Song) XORM.newInstance(mgr, Song.class);
5      song.setTitle("Fixing a Hole");
6      song.getArtists().add(track.getSong().getArtist());
7      mgr.makePersistent(song);
8      t.commit();

Line 1 gets a reference to a transaction from a PersistenceManager. Since each PersistenceManager has only one transaction associated with it, this implies that you cannot use the same PersistenceManager in different threads at the same time if you are going to be modifying objects, as they would both be using the same transaction.

Line 2 begins the transaction. XORM does not actually write out any changes made until the commit on Line 8, but any changes you make from objects associated with the PersistenceManager that owns the Transaction will be associated with the new transaction and be written out to the DB when the Transaction is committed.

Line 3 changes the title of the song for the track.

Line 4 creates a new Song object. XORM tries to be JDO compliant, but JDO does not specify how to create objects, assuming you will create them using the normal new method. Because XORM works with interfaces (see next section), it provides a factory method for creating objects. The method is static on the XORM class.

Line 5 sets the title of the new song.

Line 6 gets the collection of Artists associated with the new song and adds a reference to the same artist as the original track. XORM automatically takes care of dealing with the DB details such as whether it's a one-to-many or a many-to-many with a relationship-mapping table. Note also that even though we did not initialize the collection of Artists to a new instance of java.util.Collection (there's actually no setArtists() method on the Song interface), XORM will return an empty collection the first time getArtists() is called.

Line 7 is a special call telling the PersistenceManager that you want the passed in object to be stored. If you do not call this method, the object is considered a transient object that will be garbage collected normally and not stored to the backing datastore.

Line 8 commits all the changes we've made so far to the DB. It updates the first Song with the new title and inserts a new Song object with a join table reference to the performing Artist and the title set on Line 5.

XORM Overview

XORM is an object to relational mapping layer that provides interface-based persistence. All objects are created by XORM and are referenced in applications using only interfaces or abstract classes.

This means there is a strict separation of the data model and logic of the application, while providing an object oriented abstraction of the data allowing developers to simply use the objects without worrying about the actual data store.

XORM tries to be JDO (Java Data Objects) compliant where possible but is not a fully JDO compliant implementation.

XORM and JDO

When writing an application that uses XORM (see examples above) you will need to import the JDO interfaces. The most common JDO interfaces to import are shown below:

import javax.jdo.PersistenceManagerFactory;
import javax.jdo.PersistenceManager;
import javax.jdo.JDOHelper;
import javax.jdo.Transaction;
import javax.jdo.Query;

Those imports provide access to most of the base functionality, including everything shown in the examples.

JDO generally assumes that objects are created normally using the standard new construct. However, XORM differs from this in that it manages all the objects for you, so the application only deals with the interfaces.

To support this, XORM provides a public class that is used to bootstrap XORM and is used to provide the non-JDO functionality that is required. To get the XORM class, you need to use the following import:

import org.xorm.XORM;

From the transaction example above you can see we called the newInstance method on XORM when we wanted to create an object.

The XORM class also provides bootstrap methods named newPersistenceManagerFactory that are used to get a JDO PersistenceManagerFactory. After getting the factory, you can use standard JDO get the PersistenceManager and make queries, use transactions, etc. For maximum portability, however, you should instead use the javax.jdo.JDOHelper method getPersistenceManagerFactory() to acquire a factory. This method is supported in XORM but is slightly more verbose.

The XORM class also provides some methods you will use less often, such as getting an object that represents a persistent class's primary key. You can use that with the PersistenceManager's getObjectById call to retrieve a specific object if you just know its primary key. Normally you use a query or an object traversal to get to a specific object rather than doing a lookup by key.

The XORM class also provides access to the class and DB mapping (called ModelMapping) which lets you perform some advanced queries that aren't possible using the JDO query language (or XORM's implementation of the JDO query language). This usage should be avoided where possible and is not discussed here; see the user's guide for details.

Creating a PersistenceManagerFactory

The first step in using XORM is to create a PersistenceManagerFactory. When you create a factory you need to pass it the configuration values to use. Every PersistenceManager created by that factory will use the same configuration values.

XORM configuration values are specified using the standard Java properties file.

XORM provides several means of passing in the properties file, reflected in the many newPersistenceManagerFactory methods it provides. See the JavaDocs for detailed info. But you can create factories using the default property file xorm.properties, by specifying the name of a property file, or by passing in an input stream that contains a property file. XORM will try and read in the passed in file and if it can't find it, it will try to find it in the classpath.

For example, creating a factory using the properties in the file jdo.properties:

PersistenceManagerFactory factory = 
  XORM.newPersistenceManagerFactory("jdo.properties");

Configuring XORM

To run XORM you need to provide several different files including a property file, a model file, and a database file. Each of these files is discussed in their own section below. In addition, for each class defined in the model file, you must provide an interface for the class with appropriate get/set methods. These interface files are discussed at the end of the XORM configuration section.

Property File

The XORM configuration file that is passed to the newPersistenceManagerFactory method must have the following fields:

Property Name Example Value
javax.jdo.PersistenceManagerFactoryClass org.xorm.InterfaceManagerFactory
The class the JDO service provider makes available that implements the factory interface
javax.jdo.option.ConnectionURL jdbc:postgresql://localhost/xorm
The JDBC connection URL
javax.jdo.option.ConnectionUserName Administrator
Name of an authorized DB user
javax.jdo.option.ConnectionPassword blah
Password of user specified above
javax.jdo.option.ConnectionDriverName org.postgresql.Driver
The JDBC Driver Manager
javax.jdo.option.MinPool 2
Minimum number of DB connections to have open
javax.jdo.option.MaxPool 5
Maximum number of DB Connections to have open
org.xorm.datastore.database /com/mycompany/model-db.xml
The resource path to the XML file describing the tables and columns in your database

As you can see, the parameters are JDO defined parameters that describe the underlying database to use.

Configuration File Example:

# These are the properties specified by the JDO 1.0 specification
javax.jdo.PersistenceManagerFactoryClass=org.xorm.InterfaceManagerFactory
javax.jdo.option.ConnectionURL=jdbc:postgresql://localhost/xorm
javax.jdo.option.ConnectionUserName=Administrator
javax.jdo.option.ConnectionPassword=
javax.jdo.option.ConnectionDriverName=org.postgresql.Driver
javax.jdo.option.MinPool=2
javax.jdo.option.MaxPool=5

# This property is required by XORM
org.xorm.datastore.database=/com/mycompany/model-db.xml

XORM provides database support for a variety of databases. It determines which internal driver to use based on the value you provide for the ConnectionDriverName. XORM currently supports the following databases:

ConnectionDriverName Database Supported org.postgresql.Driver PostgreSQL oracle.jdbc.driver.OracleDriver Oracle com.microsoft.jdbc.sqlserver.SQLServerDriver Microsoft SQL Server Engine com.sybase.jdbc2.jdbc.SybDriver Sybase org.hsqldb.jdbcDriver HSQLDB com.mysql.jdbc.Driver MySQL

Database File

The XORM database file describes all the tables and columns that XORM needs to be aware of in the database. Any column not included here cannot be referenced from XORM or the application through XORM and will not be set on inserts.

The file is an XML file; the XORM distribution contains a simple DTD for it. The format is very simple:

<database>

  <table name="tablename1">
    <column name="column1"/>
    <column name="column2" primary-key="true" sequence="s_column2"/>
    <column name="column3"/>
  </table>

  <table name="tablename2">
    <column name="column1"/>
    <column name="column2" primary-key="true" auto="true"/>
    <column name="column3"/>
  </table>

  <!-- and so on -->

</database>

The file simply lists all the tables and columns. If a table has a primary key, you can add the primary-key attribute and set it to true. In addition, you need to define what kind of primary-key it is (XORM assumes they are always numeric). It supports two kinds of keys: keys that are derived by querying a sequence (see the sequence attribute on column2 in tablename1 above), or keys that are automatically added by the database (see the auto attribute on column2 in tablename2 above). Generally, all your keys will have either sequence or autoincrement for every primary key in the file, but which to use depends on the underlying database. Also refer to the User Guide and the org.xorm.datastore.option.sequenceNamePattern configuration parameter to see how to use a global name pattern for sequence names.

The file can be named whatever you wish; it is referenced in the Model Mapping file.

Here is an example using the class diagram at the top of the document as an example:

<?xml version="1.0" encoding="UTF-8"?>
<database>
  <table name="ARTIST">
    <column primary-key="true" sequence="S_ARTIST_ID" name="ID" />
    <column name="NAME" />
  </table>
  <table name="SONG">
    <column primary-key="true" sequence="S_SONG_ID" name="ID" />
    <column name="TITLE" />
  </table>
  <table name="ARTIST_SONG">
    <column name="ARTIST_ID" />
    <column name="SONG_ID" />
  </table>
  <table name="ALBUM">
    <column name="TITLE" />
    <column name="ID" primary-key="true" sequence="S_ALBUM_ID" />
  </table>
  <table name="TRACK">
    <column name="NUMBER" />
    <column name="ID" primary-key="true" sequence="S_TRACK_ID" />
    <column name="SONG_ID" />
    <column name="ALBUM_ID" />
  </table>
</database>

This database schema has four entity tables and one many-to-many linking table. By convention, we have used the "_ID" suffix to designate foreign key columns. Note that XORM doesn't require you to specify which columns are foreign keys in the database schema file.

Model Mapping File

Like the DB file, this file is also an XML file. It uses the JDO syntax, with some XORM extensions.

It's easiest to illustrate with an example.

<?xml version="1.0" encoding="UTF-8"?>
<jdo>
  <package name="com.mycompany.model">
    <class name="Artist">
      <extension vendor-name="XORM" key="table" value="ARTIST" />
      <field name="songs">
        <collection element-type="Song">
          <extension vendor-name="XORM" key="table" value="ARTIST_SONG" />
          <extension vendor-name="XORM" key="source" value="ARTIST_ID" />
          <extension vendor-name="XORM" key="target" value="SONG_ID" />
        </collection>
      </field>
      <field name="name">
        <extension vendor-name="XORM" key="column" value="NAME" />
      </field>
    </class>
    <class name="Album">
      <extension vendor-name="XORM" key="table" value="ALBUM" />
      <field name="tracks">
        <collection element-type="Track">
          <extension vendor-name="XORM" key="table" value="TRACK" />
          <extension vendor-name="XORM" key="source" value="ALBUM_ID" />
        </collection>
      </field>
      <field name="title">
        <extension vendor-name="XORM" key="column" value="TITLE" />
      </field>
    </class>
    <class name="Song">
      <extension vendor-name="XORM" key="table" value="SONG" />
      <field name="artists">
        <collection element-type="Artist">
          <extension vendor-name="XORM" key="table" value="ARTIST_SONG" />
          <extension vendor-name="XORM" key="source" value="SONG_ID" />
          <extension vendor-name="XORM" key="target" value="ARTIST_ID" />
        </collection>
      </field>
      <field name="title">
        <extension vendor-name="XORM" key="column" value="TITLE" />
      </field>
    </class>
    <class name="Track">
      <extension vendor-name="XORM" key="table" value="TRACK" />
      <field name="number">
        <extension vendor-name="XORM" key="column" value="NUMBER" />
      </field>
      <field name="song">
        <extension vendor-name="XORM" key="column" value="SONG_ID" />
      </field>
      <field name="album">
        <extension vendor-name="XORM" key="column" value="ALBUM_ID" />
      </field>
    </class>
  </package>
</jdo>

XORM expects to find an interface or abstract class in the package with the name specified in the package element, for each class defined in this file. The interface should have the exact same name, including case, as the name of the class in this file.

The extension elements are used to insert XORM specific information, usually mapping a field in a class to a column defined in the Database file described in the section above.

The extension located inside each class element maps a class to a table defined in the database file. Similarly, the extension in the field element maps the element to a specified column. The table and column names must match the definitions in the database XML file specified in the .properties file.

Every field is bound to a get or/and set method in the java file.

To be enhaced a get method must not have args and a set method must be declared to return void

And remember only abstract methods will be enhaced

XORM assumes that fields and methods follow normal Java variable name capitalization. This normally means converting from the first character after get/set from upper case to lower case, but in the (unusual) special case when there is more than one character and both the first and second characters are upper case, we leave it alone.

Thus the method "getFooBah()" becomes the field "fooBah" and "getX()" becomes "x", but "getURL()" stays as "URL".

Relationship Mapping

Things get a bit more complicated when describing relationships to other classes.

Mapping to a Single Instance

If the class we are describing has a relationship to one and only one instance of another class, the field element appears identical to any other class field (like title, etc). XORM can tell by the interface (described below) that the property returns an interface rather than a core Java type (String, Date, int, boolean, long, BigDecimal, etc). When it sees that, it automatically maps the foreign key to the class via the return type.

For example, on the class Track, there is a field, album. In the Track.java interface the property appears like:

Album getAlbum();
void setAlbum(Album album);

In the model mapping file the property appears like:

<field name="album">
  <extension vendor-name="XORM" key="column" value="ALBUM_ID" />
</field>

XORM will automatically map the ALBUM_ID to the ALBUM table.

Mapping a One-To-Many Relationship

When describing a One-To-Many relationship in XORM, you need to tell XORM what the target table is and what the foreign key in the target table is that maps to the source object.

This is because the interface will be returning a Collection of objects, so it can't infer the type from the return class like it can when traversing the simple case of a mapping to a single instance.

Here we have the relationship on the Album class that describes an Album as having a collection of Tracks.

<field name="tracks">
  <collection element-type="Track">
    <extension vendor-name="XORM" key="table" value="TRACK" />
    <extension vendor-name="XORM" key="source" value="ALBUM_ID" />
  </collection>
</field>

Here the XORM extension defines the collection as coming from the TRACK table, and telling XORM (using the source key) that it could find the Tracks for this Album via the ALBUM_ID column on the TRACK table.

Many-To-Many Mapping

Unlike a one-to-many mapping, a many-to-many mapping relies on a mapping table that contains the keys of both the source and the target tables. In model mapping file it looks similar to a one-to-many, but contains an extra XORM extension that describes the target table since the source and target tables are no longer the same.

Here is an example of a many-to-many mapping for the Song class. This mapping shows that a Song can have many Artists performing it.

<field name="artists">
  <collection element-type="Artist">
    <extension vendor-name="XORM" key="table" value="ARTIST_SONG" />
    <extension vendor-name="XORM" key="source" value="SONG_ID" />
    <extension vendor-name="XORM" key="target" value="ARTIST_ID" />
  </collection>
</field>

The "table" extension tells XORM what mapping table to use, while the "source" extension lets XORM know what key to use to find the rows used by source (in this case the Song class). The new extension is the "target", which says how to map from the mapping table to the target table, in this case the Artist class.

In XORM, all relationships are one way. In other words, if you need to traverse a relationship in both directions, you will need to define the relationship separately in both classes that are involved.

Interfaces

The last set of files that have to be in place to use XORM are the interfaces. XORM does not generate these from the XML files, they must be written by hand.

Each field (or property) defined for a class in the model mapping file must be declared in a corresponding interface.

You may have noticed that none of the files we have looked at so far have declared any type information, other than collections for many relationships. XORM uses the return type of the property to determine the type.

Here is an example of a XORM interface for the Song class:

package com.mycompany.model;

import java.util.Collection;

/**
 * A musical work.
 */
public interface Song {
    String getTitle();
    void setTitle(String title);

    Collection getArtists();
}

Properties have a get and set defined for them returning and accepting the appropriate type. Properties can be read-only or write-only simply by leaving off the matching set or get as appropriate.

Properties that represent other objects have their get method return the appropriate interface, like the get/setAlbum in the example above.

Properties that represent "many" relationships have only a get defined for them that returns a Collection. You can use the collection's add() or remove() methods to add and remove objects, and you can use the iterator() method to iterate through the instances in a collection.

If there are common properties that many interfaces will share, you can define them in their own interface and have the XORM class interface extend them. For example, you could create an interface called Titled and have anything with get/setTitle() extend it.

The interface must be located in the package that is defined by the name attribute on the package element in the model mapping file. See the model mapping file description above for details.

The following types are considered built-in persistable types (you don't need to define an interface or declare them in the model mapping file, you can just use them).

Type Wrapper (if any)
boolean java.lang.Boolean
byte java.lang.Byte
char java.lang.Character
double java.lang.Double
float java.lang.Float
int java.lang.Integer
long java.lang.Long
java.lang.String  
java.math.BigDecimal  
java.util.Date  

Note that the interface can define the wrapper or the base type, XORM doesn't care.

You can also define abstract classes for your data model objects. XORM will interpret any abstract methods as properties it should treat as persistent. Using this approach, you can provide helper methods or change the visibility of the datastore values to protected or package-protected. Here's the same example created as an abstract class with a helper method:

package com.mycompany.model;

import java.util.Collection;

/**
 * A musical work.
 */
public abstract class Song {
    public abstract String getTitle();
    public abstract void setTitle(String title);

    public abstract Collection getArtists();

    // Helper method
    public Artist getFirstArtist() {
      if (getArtists().isEmpty()) return null;
      return (Artist) getArtists().iterator().next();
    }
}

Helper methods are useful for adding custom formatting and implementing object-oriented business logic that can be accomplished with a knowledge of the object's relationships.

Summary

With your XORM config file, database file, JDO metadata file, and interfaces for each class described in the model mapping file, you are ready to actually use XORM.

Miscellaneous Info

  • XORM has a feature that if the first thing you do with a XORM returned collection is call size(), it will perform a COUNT on the objects rather than actually fetching them. This can give a big performance boost if all you care about is the size of the collection.
  • If you have a many-to-many mapping table, you should avoid declaring any columns on the table in your database file, except for the foreign keys to the tables being mapped. Since mapping tables do not have unique keys, XORM attempts to update and delete rows by issuing a where clause with all known values specified. Tables with primary keys do not have this problem since XORM just uses the primary key.
    • For example, do not put a date modified and/or date created on a mapping table unless you have DB triggers or other means to ensure those values are never null.
$Header: /cvsroot/xorm/xorm/docs/tutorial.html,v 1.12 2005/01/12 17:31:13 seifertd Exp $