Tuesday, July 5, 2011

Mapping Entities with Inheritance

Again there are a number of ways Hibernate can handle inheritance. These are based on the standard techniques for reducing generalisation hierarchies in entity-relationship diagrams.

The simplest is to use one table for the whole hierarchy. With this design, each row of the table can hold an object of any type from the hierarchy. There is one column for each of the properties in the union of the sets of properties of all the classes in the hierarchy and there is one discriminator column which contains a value (usually of type string, character or integer) used to tell which actual type of object is stored in this particular row. One normally does not make this discriminator a property of the class: it is used only by Hibernate to record and detect the type of the object that a row represents.
An Inheritance Class Hierarchy


<class name="Person" table="people" discriminator-value="P">
    <id name="id" column="id" type="long">
        <generator class="sequence"/>
    </id>
    <version  name="version"         column="version"/>
    <discriminator column="subclass" type="character"/>
    <property name="dateOfBirth"     column="dob" type="date"/>
    <property name="name"/>
    <property name="gender"/>
    <subclass name="Lecturer" discriminator-value="L">
        <property name="office" type="string"/>
        <property name="telephone" type="string"/>
    </subclass>
    <subclass name="Student" discriminator-value="D">
        <property name="studentID" type="integer"/>
    </subclass>
</class>

Here one may not specify any of the subclass fields as not null because the corresponding column will be null in the table for any object of the hierarchy which is not of the subclass that contains the relevant property for that column.

Many-to-One, Unidirectional Associations

This corresponds to the standard Java reference to one object from another.
A Many-to-One, Unidirectional Association

In the diagram above, we represent the relationship between students and their thesis supervisors. In this design, a student can have no more than one supervisor but may not (yet) have any. However, a lecturer may have any number, including zero, of students to supervise. Furthermore, we only allow a one directional link: Student has a property (say getSupervisor/setSupervisor) but there is no direct way, starting with a Lecturer object, to find the students that the lecturer supervises.
If we start with the simple, non-related, base entity mapping files for Student and Lecturer, we add this association by adding the following, as a sub-element of the class element, to the mapping file for Student:

<many-to-one name="supervisor"    column="supervisor"/>

This element acts very much like a normal property element in that it defines the mapping between the supervisor property of Student and the column in the students table. However, it also sets up the relationship so that, after getting a Student object from the database, if we use the supervisor accessor of that Student object, we will get the corresponding Lecturer object (or a proxy thereof if we have enabled lazy loading of the Lecturer objects). Finally, it ensures that the underlying database is created with a foreign key constraint that the supervisor column is a foreign key into the lecturers table.
As things stand, there is now a question of what you want the cascade behaviour of the relationship to be (see the section on cascade above). Without adding the optional cascade attribute to the many-to-one element, then the Lecturer object on the other end of the association is ignored when the Student object is saved, updated, deleted or when its supervisor property is reset away from it. Certainly we would not want the lecturer to be removed from the database when the student is deleted or when the student no longer has that lecturer as his or her supervisor; so none of the delete or all options are appropriate. But what about save-update?. There are two scenarios under which this might have an effect:
  1. If you create a new (transient) lecturer and make a persistent student refer to it. In fact, for this particular object design, one would never do such a thing: the obvious semantics of the situation dictate that you cannot just invent new lecturers on demand: you would always have to have the lecturer as a currently existing object in the database before setting the student's supervisor property to that lecturer. Since the scenario will never arise, this is neither a vote for or against using the save-update option.
  2. If the Student, and associated Lecturer objects were detached, and now you reattach the Student object, then you need the save-update option if you want the Lecturer object to be reattached automatically. Without that, you need to reattach it directly yourself — an easy task to overlook and therefore a source of bugs. This therefore, is a vote for set the cascade="save-update" option.

Note that you can specify unique="true" as an attribute of the many-to-one element. This has the effect of disallowing the possibility of having two student rows with the same supervisor values, i.e., turning the "*" on the Student side of the class diagram into a "0..1" or limiting each lecturer to having at most one supervisee. Similarly, specifying not-null="true" adds the requirement that every student must have a valid supervisor, i.e., it changes the "0..1" on the Lecturer side of the diagram to a "1".

Cascading Persistence in Hibernate

We have said, a number of times, that when an object is made persistent, that the objects it refers to are also made persistent. This was an oversimplification. In the mapping files for the classes, there is an attribute, cascade that lets us control how much, or how little, of a reference graph gets automatically persisted, deleted or updated. The values that it can be set to, and their meanings, are as follows:
  • none: no automatic action on the referenced object takes place.
  • save-update: automatically save or update the referenced object when the referencing object is saved or the transaction commits.
    delete: automatically delete the referenced object when delete() is called on the referencing object. Note that, if the referencing object is not deleted but merely removes its reference to the referenced object, then this option will not do anything and, potentially, a garbage (or orphan) object will be left in the database.
  • delete-orphan: automatically delete any object for whom the reference has been removed from the referencing object.
  • all: take the same actions as save-update and delete but not that of delete-orphan.
  • all-delete-orphan: take the same action as save-update, delete and delete-orphan.

Chitika