Download | Blog | Forums | Support | Contact us | Search  
Xcalia delivers Data Integration Software for SOA compliant data architecture data access software

 

Complex Inheritance and Collection Relational Mapping

August, 2005

In this paper, we'll go through several complex mapping features that Xcalia Intermediation Core™'s Xcalia Database Mapper supports.

Don't forget to download the source code and build files that accompany this article.

By Eric Samson & Matthew Adams

This is a demonstration of some of the more complex features that Xcalia Intermediation Core™ supports in its Xcalia Database Services (XDS) module. In it you will see how Xcalia Core's XDS supports the following concepts.

  • Optimal table-per-hierarchy class mapping
  • Collections containing heterogeneous elements, including interface types and java.lang.Object
  • Persistent user-defined interfaces
  • Classes unrelated via inheritance stored in the same table
  • Overrideable package- and/or project-level mapping strategies
  • Maps with keys of enumeration types and values of interface types

This article comes with source code and build files that require the following software to be installed and properly configured.

To run the sample, open a command prompt in the directory containing the sample's build.xml file, and execute the command "ant". This will execute the default target, "run", which will build and run the demo.

In the discussion below, the term "persistence metadata" refers to the declaration of what is to be persisted, defined in metadata files with the .jdo extension. The term "mapping metadata" refers to the declaration of exactly how persistent classes and fields are mapped to the persistent store, defined in metadata files usually beginning with the prefix "lido-" and ending with the extension ".xml". For example, for the persistence metadata file "package.jdo", the mapping metadata can be found in the file "lido-package.xml".

Optimal table-per-hierarchy class mapping

Xcalia Core's XDS supports several class mappings, including table-per-class, table-per-concrete-class, and table-per-hierarchy. When using table-per-hierarchy, class discriminators must be present in order to store the class of object that a given row stores in a table. In this case, polymorphic queries, that is, queries on the base class or some other class that is not at the bottom of the inheritance hierarchy, must use complicated SQL WHERE clauses that may not be subject to optimization, depending upon the particular underlying database.

In this sample, the class Company and its subclass PublicCompany, are stored according to the inheritance mapping strategy per-hierarchy, meaning one table stores all instances of the base class and its subclasses. Normally, a query over the extent of all instances of Company would result in a SQL WHERE clause that would use the SQL OR operator or the SQL IN operator to include all of the class discriminator values of the classes in the hierarchy:

SELECT t1.COMP_ID FROM COMPANY t1 WHERE t1.TYPE = 'COMP' OR t1.TYPE = 'COMPPUB'

or

SELECT t1.COMP_ID FROM COMPANY t1 WHERE t1.TYPE IN ( 'COMP', 'COMPPUB' )

Xcalia Core's XDS allows for a clever optimization when using class discriminators represented as strings. If the discriminator string values form a hierarchy themselves, then Core can leverage this fact by using the SQL LIKE syntax, instead of using an OR or IN construct. In this sample, the class discriminator for the base class, Company, is "COMP", and the discriminator for its subclass PublicCompany is "COMPPUB", which is the concatenation of "COMP", its superclass discriminator value, and "PUB", its own class discriminator value. In order for Core to take advantage of this and generate polymorphic queries using the SQL LIKE syntax, the inheritance mapping strategy called {like} must be specified in the mapping metadata:

<class name="Company">
   <inheritance-mapping type="per-hierarchy">
       <discriminator column="type" value="COMP" class="{like}"/>
   </inheritance-mapping>
   ...
</class>

With this declaration, the SQL query generated uses LIKE instead of OR or IN:

SELECT t1.COMP_ID FROM COMPANY t1 WHERE t1.TYPE LIKE 'COMP%'

Collections containing heterogeneous types

Xcalia Core's XDS also supports collections containing elements of heterogeneous types all within the same collection, including persistent classes, persistent interfaces, and, of course, primitive wrappers. In this case, there is a restriction that the user-defined, persistent types stored as elements in the collection use datastore identity. In the accompanying source code, here is the declaration in the persistence metadata of the heterogeneous collection field features in the class Product:

<class name="Product" persistence-capable-superclass="Vendable">
   <field name="features">
       <collection element-type="java.lang.Object" embedded-element="true"/>
   </field>
</class>

Notice that the element-type attribute of the collection element specifies java.lang.Object; this attribute is actually optional, as the default value of element-type is java.lang.Object. The embedded-element attribute is required to be true in order to store primitive and primitive wrapper types properly, as they are second class objects (SCOs), having no identity of their own; it has no effect on persistence capable types.

The mapping metadata for class Product simply adds the name of the column that stores the index in the ordered collection (which uses a java.util.List, in this case):

<class name="Product" table="PRODUCT">
    <field name="{ID}" column="PROD_CODE"/>

    <field name="features">
        <ordered-collection table="PROD_FEATURE">
            <index column="FEATURE_ORDER"/>
        </ordered-collection>
    </field>
</class&

Now, see how simply the collection is used:

Product xcaliaCore = new Product("Xcalia Intermediation Core");
// ...
xcaliaCore.addFeature("JDO 2 compliant"); // String
xcaliaCore.addFeature(xcalia); // Company
xcaliaCore.addFeature(new Float(4.0)); // Float

In order to store the heterogeneous values in the collection, the table representing the collection (PROD_FEATURE) has the following structure (using MySQL syntax, in this example):

CREATE TABLE `prod_feature` (
 `LIDOVALUE` longblob,
 `LIDOFK` varchar(30) default NULL,
 `FEATURE_ORDER` double default NULL,
 KEY `idx_PROD_FEATURE__ownerascompanyProduct` (`LIDOFK`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Note the column LIDOVALUE, which stores the heterogeneous elements as BLOBs. In this case, all primitive, primitive wrappers, and system-defined types (java.util.Date, etc.) are serialized into the column, whereas user-defined types that are persistable have their object identities serialized and stored in the column, not the objects themselves, as they are stored elsewhere in the database. If this behavior is not desired, then custom mappers can be defined by the developer to change this behavior.

Persistent user-defined interfaces

Xcalia Core's XDS fully supports the notion of persistent, user-defined interfaces. A persistent interface is one that can be used as the type of a persistent class's field, and one that may or may not have an extent that can be iterated or queried, and will include instances of persistent classes that implement the persistent interface. Additionally, queries may include expressions that reference fields of persistent interface types.

To define a persistent interface, simply define the regular Java interface as you would normally, then add the interface element within the interface's package element in the persistence metadata, then declare in the persistence metadata that a given class implements the persistent interface. In the code that accompanies this example, the declarations

<interface name="Billable"/>

<class name="VariableContract">
    <implements name="Billable"/>
</class>

<class name="FixedContract">
    <implements name="Billable"/>
</class>
                        

name the interface Billable to be persistent and declare that persistent classes VariableContract and FixedContract both implement the Billable interface. With this declaration, persistent classes that have fields of type Billable will be stored properly and can be queried for, and will contain references to persistent classes VariableContract or FixedContract.

The following query is an example of querying for an interface-typed field:

q = pm.newQuery(Service.class);
q.declareParameters("company.Billable billable");
// billables is a Map whose values are of type Billable, an interface
q.setFilter("billables.containsValue(billable)");
q.setUnique(true);
// training is a variable of type VariableContract, which implements Billable
Service service = (Service) q.execute(training);

The resultant SQL produced by the mapping is

SELECT t1.SERVICE_CODE,t1.name,t1.company,t1.price
FROM SERVICE t1,SERVICE_BILLABLES t2
WHERE t2.LIDOVALUE=? AND t2.LIDOFK=t1.SERVICE_CODE

Classes unrelated via inheritance stored in the same table

An important aspect to note about persistent interfaces is that any persistent class can implement them, regardless of their inheritance relationship. In the case of the accompanying code, there are two classes, VariableContract and FixedContract, unrelated via inheritance, that implement the interface, but are stored in the same table; with this design, any classes that contain a collection of Billables can use the mapping {reverse} for simple reverse foreign key mapping, just as you would if they were the same class or classes in the same inheritance hiearchy mapped with the per-hierarchy mapping strategy. There is no requirement that persistable classes that implement persistent interfaces be stored in the same table. The accompanying code simply demonstrates that Xcalia Core's XDS supports mapping multiple otherwise unrelated classes to the same table.

As can be seen in the mapping metadata, both VariableContract and FixedContract are stored in the table BILLABLE:

<class name="VariableContract" table="BILLABLE">
    <inheritance-mapping type="per-hierarchy">
        <discriminator column="classtype" value="VC"/>
    </inheritance-mapping>
</class>

<class name="FixedContract" table="BILLABLE">
    <inheritance-mapping type="per-hierarchy">
        <discriminator column="classtype" value="FC"/>
    </inheritance-mapping>
</class>

This results in the table BILLABLE with the following structure (in MySQL syntax):

CREATE TABLE `billable` (
 `yearlyRate` float default NULL,
 `ratePerUnitTime` int(11) default NULL,
 `classtype` varchar(50) default NULL,
 `time` int(11) default NULL,
 `inceptionDate` datetime default NULL,
 `LIDOID` varchar(30) default NULL,
 KEY `idx_BILLABLE_discriminator` (`classtype`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

The table then can store instances of both classes even though they are otherwise unrelated. After running the example, here are the instances found in the table:

BILLABLE
yearlyRateratePerUnitTimeclasstypetimeinceptionDateLIDOID
2000(null)FC(null)2005-08-12 09:35:29FC:5
(null)1000VC3(null)VC:6
(null)2000VC2(null)VC:7

 

Overrideable package- and/or project-level mapping strategies

Another useful feature of Xcalia Core's XDS is the ability to define different mapping strategies at levels higher than field and class. For example, if you wish to have all strings mapped by default to database VARCHAR columns of a certain length, you can specify this at the package or project level, then override that setting on a class or field basis. In the accompanying code, for example, all strings are defined to be of length 50 unless otherwise set in a <class> or <field> element:

<package name="company">
    <mapping-strategy type="java.lang.String">
        <mapper size="50"/>
    </mapping-strategy>
    ...

This is overridden, however, in the Company class's name field, which is set to be of length 255:

<class name="Company" table="COMP">
    ...

    <field name="name">
        <mapper size="255"/>
    </field>

Examining the structure of the COMPANY table, you can see that all string columns, which happen to be part of the embedded class Address, have string lengths of 50, except for column name, which has a length of 255 (again, in MySQL syntax):

CREATE TABLE `comp` (
    `COMP_ID` varchar(30) NOT NULL default '',
    `address_street` varchar(50) default NULL,
    `type` varchar(50) default NULL,
    `stockValue` float default NULL,
    `address_zip` varchar(50) default NULL,
    `address_country` varchar(50) default NULL,
    `name` varchar(255) default NULL,
    `logo` longblob,
    `address_city` varchar(50) default NULL,
    PRIMARY KEY  (`COMP_ID`),
    KEY `idx_COMP_discriminator` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Most of XDS's mapping strategies can be scoped in this manner, offering a convenient means to set default mapping strategies for many elements in a concise way.

Maps with keys of enumeration types and values of interface types

The accompanying code has a particularly interesting example of a complex map, one that has as its keys instances of the enumeration class BillableType and as its values instances of the persistent interface Billable. This is declared in the persistence metadata:

<class name="Service" persistence-capable-superclass="Vendable">
   <field name="billables">
       <map key-type="BillableType" value-type="Billable" embedded-key="true"/>
   </field>
</class>
Keys of enumeration types

The embedded-key attribute declares that instances of keys are stored directly in the collection. This is perfectly fine, because in this case, the keys are actually not instances of persistent classes, they're instances of transient enumeration classes, which we'll map using one of Xcalia Core XDS's custom mapping features:

<class name="Service" table="SERVICE">
   ...
    <field name="billables">
       <keyed-collection>
           <key>
               <mapper class="company.BillableTypeMapper"/>
           </key>
       </keyed-collection>
   </field>
</class>

This mapping uses a custom class that we authored to perform the mapping; such mappers must implement the interface xcalia.lido.api.mapper.CustomMapper. It maps between the in-memory representation of an object (in this case, an instance of BillableType) and its persistent representation (in this case, an integral value). Here's the implementation of our custom mapper class:

package company;

import xcalia.lido.api.mapper.CustomMapper;

public class BillableTypeMapper implements CustomMapper {

    // Returns the Class object for the type stored in the database
    public Class getStorageType() {
        return Integer.class;
    }

    // Returns the enumeration class given the database value
    public Object fromStorage(Object value) {
        return BillableType.from((Integer) value);
    }

    // Returns the value to store in the database
    public Object toStorage(Object billableType
        return ((BillableType) billableType).getValueAsObject();
    }
}
Values of interface types

The values held in the map are interesting in that they are essentially heterogeneous values, being independent classes in terms of inheritance that happen to implement a persistent interface. In fact, the previous discussion of heterogeneous collections is a general case of this one; the only additional restriction here, imposed by ourselves, is that the classes implement Billable.

Everything that is required is already declared in the persistence metadata shown above; there is no special mapping required. As shown in a previous section, the two classes that implement Billable are VariableContract and FixedContract, instances of which are stored in the table BILLABLE. Xcalia Core's XDS takes care of keeping track of the interface references in the map's values automatically.

Summary

As the points in the article demonstrate, Xcalia Core, via its Xcalia Database Services (XDS) module, fully supports very rich mapping options, especially in terms of inheritance, interface implementation, collections, and maps. This allows you to utilize all standard Java idioms in your code, without having to worry so much about whether they can be mapped to the database, which, in turn, will help you create richer and more feature-complete applications sooner.

Xcalia is a major SOA vendor addressing enterprise IT and business requirements regarding business oriented enterprise data access,