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 Object Hierarchies to Relational Tables

I use the term object hierarchy to refer to the parent-child hierarchy of class instances where an object has one or more objects as data members. In the ShippingLine example, the children of ShippingLine objects are instances of class Ship; Ship instances, in turn, have PortOfCall child objects as data members.

There are several ways to map such hierarchies to a relational database table. Each type of mapping is determined by factors such as the cardinality of parent-child relationship (one-to-one, one-to-many, many-to-many), and if children represent strong or weak entities.

Entities Both Strong and Weak

One of the primary constraints to be determined in any business model is the relative independence of data entities (represented as classes and their instances in Java). Some entities come into and out of existence of their own accord. For instance, new shipping lines are founded and others go out of business as time passes. We want to be able to create new ShippingLine classes at the drop of a hat. Shipping lines are strong entities in our data model. When a new shipping line comes into existence, I can construct a ShippingLine object to contain information about it.

There are some entities in our model that don't have such a carefree existence. Such an entity is so dependent upon another that it couldn't exist without the existence of the referenced entity. When an entity instance depends upon another for its very existence, it is a weak entity. Ships are weak entities and must be associated with a shipping line before we can incorporate ship information in our model.

Note that I'm referring to business rules in the abstract, and not to rules applicable to specific physical data models. There are two physical models that this article focuses upon: one in Java and one in a SQL database. If we tossed in XML documents, that would be a third. Each physical model has different mechanisms for enforcement of weak entity dependencies. In Java, a Ship's dependency upon a ShippingLine would usually be enforced by requiring a non-null ShippingLine.object parameter passed to the Ship class constructor. In the database, such a dependency would be enforced by having a required foreign key column in the ship table that has a value that matches a primary key in the ShippingLine table. In XML, the shippingLine element might be the root element of a document, and ships are child elements.

In fact, there may be different constraints for any one of these physical data models. For example, an XML document might report on all shipping lines in our database, and therefore would require a different root element. That's not a business constraint, however, so much as a constraint enforced by the physical data model representation. Those types of dependencies are best expressed locally; in this instance, the reporting module would be sure that all shippingLine elements are within the document's root element (whatever that may be).

I can go ahead and use the particular mechanisms that each physical model provides to enforce strong-strong and strong-weak entity constraints. There are drawbacks to this approach, however. The obvious one is that I must make sure that each mechanism is in fact enforcing these constraints in a manner compatible with the others. Also, changes in the strong-weak relationship of entities requires changes to schema and class definitions at separate locations. A more subtle drawback is that constraint mechanisms of a particular data model may be incompatible with the application's mechanisms for data storage and class construction. In fact, Castor's method of data binding often runs into trouble when columns in a database are defined with sophisticated column constraints.

Fortunately, the Castor mapping file has all of the appropriate features to ensure that entity relationships, both strong-strong and strong-weak, can be defined and enforced.

Dependent Classes

Castor uses the term dependent class to denote a class representing a weak entity. Instances of a dependent class have a parent master object that they are dependent upon. Through the use of a depends attribute in the class mapping element, the JDO persistence engine is able to ensure that a dependent class instance is only saved as part of its master object. In other words, you can't save a dependent class by itself.

There are other restrictions noted in the Castor documentation:

  • A dependent class object "may not be created, removed, or updated separately [from its master object]."
  • "... Castor doesn't allow a dependent class object to change its master object instance during a [database] transaction."
  • "Each dependent object can have only one master object."
  • "Both dependent and master objects must have identities." This includes identities generated by key-generators.

Before moving on, let's note two further advantages of enforcing dependencies through the mapping file.

  1. All depenencies are noted in one location. This makes maintenance easier when dependency relationships change.
  2. Dependent class objects do not require a separate save operation. When the dependent object's parent is saved, all of its depenencies will be saved, too.

Related Classes

Not all parent-child object relationships are strong-weak. A class representing a strong entity in the data model can refer to another strong entity class. A shipping line might be owned by a shipping magnate, or by a holding company. Both magnates and holding companies might be strong entities in our data model (they're certainly not dependent upon shipping lines for their existence (although, arguably, the magnate is)). Conversely, a shipping line may be owned by a magnate, or by a holding company, or it might be a wholly independent corporation -- so its existence is not dependent upon magnates or holding companies.

In such circumstances, even though one class might refer to the other, there isn't a strong-weak dependency there; it's possible to create magnates independently of shipping lines and vice versa. Both are strong entities in our model. For these cases, Castor declares them to be related classes. Related classes are not treated specially by Castor. This means that they come and go out of existence without regard for each other, and (most importantly) they must be saved independently.

One-to-many Relationship

A one-to-many relationship in Java is simply a class instance with a collection of objects of the same type. In a database, the one-to-many relationship is denoted by the appearance of identical foreign key values in a table (each row with the same foreign key value points to a single row in different table).

In the mapping file, fields that are collections of same-type objects are denoted by the collection attribute. The collection attribute can take one of several values:

Mapping Name java.util.collection
arraylist ArrayList
hashtable Hashtable
map Map (HashMap)
set Set (HashSet)
vector Vector

(Note: The classes in parenthesis are the default implementations of the interface shown. Defaults are used where collection instances are null.)

One consequence of a shipping line having one or more ships is that the ShippingLine class must have a collection whose elements are Ships. Let's say the collection is a Java ArrayList. The mapping file below highlights the way collections are mapped in class elements:

<class name="ShippingLine" identity="id" key-generator="seqgen">
  <map-to table="shipping_line"/> 
  <field name="id" type="integer" get-method="getID" set-method="setID"> 
    <sql name="id" type="integer"/> 
  </field> 
  <field name="name" type="string" required="true"> 
    <sql name="name" type="varchar"/>
  </field>
  <field name="ships" type="Ship" collection="arraylist>
    <sql many-key="id_shipping_line"/>
  </field> 
</class>

<class name="Ship" identity="registry name" depends="ShippingLine">
  <map-to table="ship"/> 
  <field name="registry" type="string" required="true">
    <sql name="registry" type="varchar"/> 
  </field> 
  <field name="name" type="string" required="true"> 
     <sql name="name" type="varchar"/> 
  </field> 
  <field name="shippingLine" type="ShippingLine" required="true"> 
    <sql name="id_shipping_line"/>
  </field> 

  <!-- rest of fields mapped here --> 
</class>

Here, we have a dependent one-to-many relationship between ShippingLine and Ship. This means that the collection of Ships will be saved automatically at the time ShippingLine is saved. When mapping the collection to a database table, the many_key attribute is used in the sql element. The many_key value refers to the column in the ship table that contains the foreign key to shipping_line. Let me restate that for emphasis: the shipping_line mapping refers to a foreign key column in the ship table. This value must match that of the name attribute in the sql element for the Ship class mapping (highlighted).

The foreign key column type of ship must match the type of the identifier (primary key) it refers to. Since the shipping_line table has a primary key on the id column (an integer), the foreign key id_shipping_line must also be of that type. However, note that this type is not shown in the field mapping for shippingLine. The type is inferred from the field's declared type. That type is not integer, but ShippingLine. This is the one case where the type of the field does not correlate with the type of the table column.

It's not enough, however, that I declare an ArrayList in ShippingLine to store Ship objects. I have to maintain a backlink to the master object from the dependent object. This is simple enough to do. First, I add a ShippingLine class instance to the Ship class, with get/set methods:

class Ship
{
  private ShippingLine _shippingLine;
  ...
  public ShippingLine getShippingLine() 
  { 
    return _shippingLine;
  }

  public void setShippingLine(ShippingLine shippingLine)
  { 
    _shippingLine = shippingLine;
  }
  ...
}

Next, I need to be sure that the reference to the master object is populated at the appropriate time. The logical place to do this is in ShippingLine's addShip() method:

public void addShip(Ship vShip) 
  throws java.lang.IndexOutOfBoundsException
{ 
  _shipList.add(vShip); 
  vShip.setShippingLine(this); //set the master object reference
}

What about the removeShip() method? Should it delete the master object reference from Ship when an instance is removed from the ShippingLine? It depends on the application, but as far as we're concerned, since Ship objects can only be persisted in the database through a ShippingLine, none of the Ship instances will ever be saved with an invalid master object reference (all instances will have gone through the ShippingLine.addShip() method).

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

Next Pagearrow