Using Castor JDO for SQL Mapping
Pages: 1, 2, 3, 4, 5, 6, 7
Mapping One Class to One Database Table
As mentioned earlier, unlike Castor's XML marshalling, which has a default mapping based on Java introspection of the class definitions, a class-to-table mapping file must be provided explicitly in order for Castor to persist objects in a database. There are some tools, such as Raccoon, that can generate a Castor mapping file from Java beans. For the examples to follow, I'll author the mapping file by hand. By default, Castor expects a mapping file named mapping.xml.
SQL Mapping Basics
If you've used Castor mapping files in the past to marshal XML, then the
format for binding Java data objects to databases is will seem familiar. Here
is the mapping between the ShippingLine Java class and the shipping_line database table:
<class name="ShippingLine" identity="id">
<map-to table="shipping_line"/>
<field name="id" type="integer">
<sql name="id" type="integer"/>
</field>
<field name="name" type="string" required="true">
<sql name="name" type="varchar"/>
</field>
</class>
What follows is an explanation of the elements and their values:
The
classelement names the class to be mapped and its identity field. In cases where there are multiple fields that make up the identifier for the class (and table), a space-separated list ofidfields can be specified. Say, for instance, thatShippingLine idwas not unique, but thatid + namewould be. In that case, the identity attribute of the class element would have this value:<class name="ShippingLine" identity="id name">The
map-toelement identifies the database table that will be used to persist theShippingLineclass data.The first field to be mapped is the
idfield. Its datatype is specified asinteger, which is compatible with both the Java primitive datatypeintandclass java.lang.Integer. A type specification is not required, but by providing one, we add an extra bit of type checking during the persistence process.Values for the class data member
idare to be stored in theidcolumn of theShippingLinetable, according to thesqlchild element offield. The SQL typeintegeris compatible with that of theintegerdatatype used for the Java field.The next field to be mapped is the
namefield. One of the attributes of the field element indicates that a value for the name is required. When it comes time to persist aShippingLineobject, that object must have a name value or an exception will be thrown.The
sqlelement maps thenamefield to a like-named column inshipping_line. The type of the column is declared to bevarchar, which is compatible with thejava.lang.Stringtype of the field.
Note: Field names generally refer to methods, not the names of data members of a class.
Let's take a look at the ShippingLine class once more:
public class ShippingLine {
private String _name;
private ArrayList _ships;
private int _id;
// methods...
public int getId() { return _id; }
public void setId( int id ) { _id = id; }
//...
};
You see that the name of id data member actually has a preceding
underscore: _id. Note that we didn't see this underscore in the field name
of the class mapping. That's because Castor is not accessing the _id variable
directly. Instead, it is inferring the names of the getter/setter methods for
the _id data member from the field name provided in the class mapping. Hence,
getId() and not get_id().
It is possible, however, to access data members directly. The section "Binding Through Class Data Members" will show you how this is done.
Key Generators
In order to ensure that the id column of the shipping-line table
is unique, we defined the column to be of type SERIAL. The Postgres database
supports this nonstandard (non-SQL) type as a convenient way to specify
columns as unique identifiers for a table row. It's really more of a function
than a type. When a column of type SERIAL is declared in Postgres SQL, the
Postgres database management system creates a SEQUENCE table for that
column.
A SEQUENCE table is a special table designed specifically for the
autogeneration of incremental values. These values are most frequently used
as identifiers, or primary keys, of table rows. When the shipping_line table
was created, Postgres's SQL interpreter sees the SERIAL type of the id column
and creates a separate SEQUENCE table named shipping_line_id_seq .
Note: Just about every database has support for SEQUENCE tables,
although the syntax for using them may vary. Consult your database manual for
specifics. What follows is an explanation of how to use SEQUENCE tables in
Postgres; different databases may require slight alterations in the syntax
used below.
Having a SERIAL datatype for a column is only half of the process of the
auto-generation of keys. The remaining work to be done is to call the
auto-generating function of the sequence table prior to commiting a row
insertion. This is done by declaring a key-generator element
in the mapping.xml file. In this element, Castor has to know the name of the
SEQUENCE table in order to be able to call the proper SEQUENCE table method
for getting the next id value.
The way to specify the SEQUENCE table
name in the key-generator element is
shown below.
<key-generator name="SEQUENCE" alias="seqgen">
<param name="sequence" value="{0}_{1}_seq"/>
</key-generator>
The key-generator element has two attributes. The name attribute describes
the type of key being used. Castor supports various types of keys; SEQUENCE
keys are the most common. The alias attribute gives us a handle on the key
generator. It is possible to refer to the key generator by its name, but an
alias gives us the option of having two different SEQUENCE generators
in the mapping file, each under a separate alias.
The param child element indicates the SEQUENCE table name. Here we are
using replacable parameters, {0} and {1}, to specify the SEQUENCE table to be used. Every occurrence of the SERIAL column type requires a new sequence table to be
created. The name of the table is generated from the table name where SERIAL
occurs, along with the column name. Recall that the sequence table name for
the shipping_line id column was: shipping_line_id_seq. By using
the replaceable paramters {0} (which resolves to the table name containing
the SERIAL column) and {1} (the SERIAL column name), each time the
key-generator is called, the correct SEQUENCE table is used.
With my key-generator defined, I can next hook up a key-generator to the
shipping_line table mapping definition by adding a reference to the
key-generator via a class element attribute:
<class name="ShippingLine" identity="id">
<map-to table="shipping_line"/>
<field name="id" type="integer" key-generator="seqgen">
<sql name="id" type="integer"/>
</field>
<field name="name" type="string" required="true">
<sql name="name" type="varchar"/>
</field>
</class>
Now, every new shipping-line row added to our Postgres database will have
a unique, sequential value as an identifier.
Multifield Identifiers
Not all SQL tables have single-column keys; some keys are compound keys. A compound key uniquely identifies a row in a database table, but uses two or more columns to do so.
Let's say a ship's name uniquely identifies a ship within a
registry. That's not to say that a ship is uniquely identified by name; it is
instead identified by the compound identifier registry.name::ship.id. This
means that each ship must have a registry attribute, and that the combination of registry attribute and ship id must be unique within the XML document (regardless of which shipping line the ships are in). In XML schema, it is possible to define a unique constraint that
will enforce this for all ship elements in the document root:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="document_root">
<xs:complexType>
<xs:sequence>
<xs:element name="shippingLine" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="ship" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string"
use="required"/>
<xs:attribute name="registry" type="xs:string"
use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:unique name="shipUniq">
<xs:selector xpath="shippingLine/ship"/>
<xs:field xpath="@name"/>
<xs:field xpath="@registry"/>
</xs:unique>
</xs:element>
</xs:schema>
This same uniqueness constraint can be implemented in the database table
definition for ship:
CREATE TABLE ship (
registry VARCHAR(80) NOT NULL,
name VARCHAR(80) NOT NULL
-- other column definitions here --
CONSTRAINT id_ship UNIQUE (registry, name)
);
The database mapping file used by Castor can enforce this constraint for us, instead:
<class name="Ship" identity="registry name">
<map-to table="ship"/>
<field name="name" type="string" required="true">
<sql name="name" type="varchar"/>
</field>
<field name="registry" type="string" required="true">
<sql name="registry" type="varchar"/>
</field> <!-- rest of fields mapped here -->
</class>
The value of the identity attribute of the class element is, in this case, a
space-delimited list of fields that together uniquely identify each ship.
With the identity constraint defined here, there is no need to have a unique
constraint defined in the database table definition. Castor will handle
it.