Object-Relational Mapping with Apache Jakarta OJB
2.3 Mapping Inheritance
Here comes the real meat of the example. There are three common techniques
to map component inheritance to relational tables. They are:
Map each class onto a distinct table, each with its own base class attributes.
Map all classes in a hierarchy onto one table.
Map each class into a distinct table, each with a foreign key to its base class table, where base class attributes can be found.
Each technique has its own pros and cons. In the following sections, we will
discuss each one of them in detail.
2.3.1 One Table Per Class
2.3.1.1 Table Schema
This technique puts all base class attributes into the subclass tables. The
base class table and the subclass table are not related in the schema.
Example 3: Table Schema For Mapping Strategy One
CREATE TABLE employee (
id INTEGER NOT NULL,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE manager (
id INTEGER NOT NULL,
name VARCHAR(50) NOT NULL,
project_number INTEGER NOT NULL,
PRIMARY KEY (id)
);
2.3.1.2 Implementation
The implementation is straightforward and requires no special OJB changes:
Example 4: Implementations For Mapping Strategy One
public class EmployeeImpl extends BasePersistenceObjectImpl implements Employee
{
private String name;
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
}
public class ManagerImpl extends EmployeeImpl implements Manager
{
private Integer projectNumber;
public ManagerImpl()
{
super();
}
public Integer getProjectNumber()
{
return projectNumber;
}
public void setProjectNumber(Integer projectNumber)
{
this.projectNumber = projectNumber;
}
}
2.3.1.3 OJB Descriptor
The OJB descriptor describes how a Java class and its attributes map to a
table. In OJB, we denote inheritance using the <extent-class> element in
the descriptor file. In this case, EmployeeImpl is the base class of
ManagerImpl. Once the extent-class is in place, if a query is performed against
EmployeeImpl, objects that extend EmployeeImpl (e.g. ManagerImpl) will also be considered for matching.
Example 5: OJB Descriptor For Mapping Strategy One
<class-descriptor class="ojbtest.impl.distinct.EmployeeImpl"
table="EMPLOYEE">
<extent-class class-ref="ojbtest.impl.distinct.ManagerImpl"/>
<field-descriptor id="1"
name="id"
column="ID"
jdbc-type="INTEGER"
primarykey="true"
autoincrement="true"/>
<field-descriptor id="2"
name="name"
column="NAME"
jdbc-type="VARCHAR"/>
</class-descriptor>
<class-descriptor class="ojbtest.impl.distinct.ManagerImpl"
table="MANAGER">
<field-descriptor id="1"
name="id"
column="ID"
jdbc-type="INTEGER"
primarykey="true"
autoincrement="true"/>
<field-descriptor id="2"
name="name"
column="NAME"
jdbc-type="VARCHAR"/>
<field-descriptor id="3"
name="projectNumber"
column="PROJECT_NUMBER"
jdbc-type="INTEGER"/>
</class-descriptor>
2.3.1.4 Pros & Cons
This approach has two main advantages. First, the table schemas are separated cleanly. No joins are necessary to query one type of object. Second, database constraints can be applied.
Despite these advantages, this approach suffers from performance problems.
Consider the case when you need to query all Employees. Both the MANAGER table
and the EMPLOYEE table will be queried. The performance penalty grows
proportionally to the size of the class hierarchy. Also, the duplicated
base class columns in each subclass table make adding an attribute to the base
class more difficult.
2.3.2 One Table Per Class Hierarchy
2.3.2.1 Table Schema
With this technique, the base class and all of its subclasses are stored in
one table. The table schema contains attributes of all classes in the same
hierarchy:
Table 6: Table Schemas For Mapping Strategy Two
CREATE TABLE employee (
id INTEGER NOT NULL,
class_name VARCHAR(255),
name VARCHAR(50) NOT NULL,
project_number INTEGER,
PRIMARY KEY (id)
);
Notice that a new column, CLASS_NAME, is used by OJB to
identify the type of object a table row represents.
2.3.2.2 Implementation
To use this mapping technique, OJB requires a special attribute
(ojbConcreteClass) in your base class. This special attribute must be
initialized to the proper subclass class name during object initialization.
Example 7: Implementations For Mapping Strategy Two
public class EmployeeImpl extends BasePersistenceObjectImpl implements Employee
{
/** the special attribute telling OJB the object's concrete type.
* NOTE: this attribute MUST be called ojbConcreteClass
*/
protected String ojbConcreteClass;
private String name;
public String getOjbConcreteClass()
{
return ojbConcreteClass;
}
public void setOjbConcreteClass(String ojbConcreteClass)
{
this.ojbConcreteClass = ojbConcreteClass;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
}
public class ManagerImpl extends EmployeeImpl implements Manager
{
private Integer projectNumber;
public ManagerImpl()
{
super();
ojbConcreteClass = ManagerImpl.class.getName();
}
public Integer getProjectNumber()
{
return projectNumber;
}
public void setProjectNumber(Integer projectNumber)
{
this.projectNumber = projectNumber;
}
}
2.3.2.3 OJB Descriptor
In the descriptor, we specify that the ojbConcreteClass attribute is mapped
to the CLASS_NAME table column. This allows queries to return objects of the
right type:
Table 8: OJB Descriptor For Mapping Strategy Two
<class-descriptor class="ojbtest.impl.hierarchy.EmployeeImpl"
table="EMPLOYEE">
<extent-class class-ref="ojbtest.impl.hierarchy.ManagerImpl"/>
<field-descriptor id="1"
name="id"
column="ID"
jdbc-type="INTEGER"
primarykey="true"
autoincrement="true"/>
<field-descriptor id="2"
name="ojbConcreteClass"
column="CLASS_NAME"
jdbc-type="VARCHAR"/>
<field-descriptor id="3"
name="name"
column="NAME"
jdbc-type="VARCHAR"/>
</class-descriptor>
<class-descriptor class="ojbtest.impl.hierarchy.ManagerImpl"
table="EMPLOYEE">
<field-descriptor id="1"
name="id"
column="ID"
jdbc-type="INTEGER"
primarykey="true"
autoincrement="true"/>
<field-descriptor id="2"
name="ojbConcreteClass"
column="CLASS_NAME"
jdbc-type="VARCHAR"/>
<field-descriptor id="3"
name="name"
column="NAME"
jdbc-type="VARCHAR"/>
<field-descriptor id="4"
name="projectNumber"
column="PROJECT_NUMBER"
jdbc-type="INTEGER"/>
</class-descriptor>
2.3.2.4 Pros & Cons
This approach has two main advantages. First, no table joins are necessary
to query objects in the same hierarchy. Second, adding a new class to the
hierarchy has very little overhead.
This approach has its own problems, though. For example, database constraints must be
relaxed to accommodate all attributes in the class hierarchy. Also, it is not
easy to identify from the table schema which attributes belong to which class.
2.3.3 One Table Per Class, with a Foreign Key to the Base Class
This technique creates a table for each class in the hierarchy. Unlike the
first approach, the base class attributes are not duplicated in the subclass
tables. Instead, each subclass table uses a special foreign key to refer back
to the base class. OJB does not directly support this model.
This technique has two main advantages. First, the subclass tables do not
duplicate any base class columns. Second, database constraints can be applied.
However, this technique suffers from excessive table joins in queries. The
performance gets worse as the class hierarchy deepens.
After evaluating your options, you can decide which mapping technique suits
you best. The next step in our example is to create the OJB deployment
descriptor.