ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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:

  1. The class element 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 of id fields can be specified. Say, for instance, that ShippingLine id was not unique, but that id + name would be. In that case, the identity attribute of the class element would have this value:

        <class name="ShippingLine" identity="id name">
  2. The map-to element identifies the database table that will be used to persist the ShippingLine class data.

  3. The first field to be mapped is the id field. Its datatype is specified as integer, which is compatible with both the Java primitive datatype intand class 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.

  4. Values for the class data member id are to be stored in the id column of the ShippingLine table, according to the sql child element of field. The SQL type integer is compatible with that of the integer datatype used for the Java field.

  5. The next field to be mapped is the name field. One of the attributes of the field element indicates that a value for the name is required. When it comes time to persist a ShippingLine object, that object must have a name value or an exception will be thrown.

  6. The sql element maps the name field to a like-named column in shipping_line. The type of the column is declared to be varchar, which is compatible with the java.lang.String type 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.

Pages: 1, 2, 3, 4, 5, 6, 7

Next Pagearrow