Showing posts with label hibernate-inheritance. Show all posts
Showing posts with label hibernate-inheritance. Show all posts

Tuesday, 5 July 2011

One-to-Many, Unidirectional Associations

This relationship is essentially the same as Many to One relation, Unidirectional relation , but now we choose the opposite direction for navigating the connection. See here for Many to one unidirectional relation.

Thus our Lecturer object now has a property which is a collection of Student objects while the Student objects have no properties which refer to their supervising Lecturer.

A One-to-Many, Unidirectional Association

As far as the database is concerned, there is no difference between this and the unidirectional many-to-one association: there will still be a single column in the table holding the Student objects that contains a foreign key into the table holding the Lecturer objects.
Now that entities are being stored in collections, it becomes critical that you have appropriately implemented equals and hashCode methods for those entities. In particular, you should ensure that these methods are independent of the generated surrogate keys and that trivial changes to the object do not effect the methods while the objects are in the collections.
The simplest collection, for our purposes, is a Set. To create the association, we add a Set valued property to Lecturer
 
<set name="advisees" cascade="save-update" lazy="true">
<key column="lecturer_id"/>
<one-to-many class="Student"/>
</set>

Here, we define a property of Lecturer which is Set valued. The name of the property is advisees. This property is to capture a one-to-many association to the Student class and it this association is to be implemented in the database as a foreign key to the table holding Lecturer objects stored in the column lecturer_id in the table holding Student objects.
There are a number of constraints imposed by the use of this one-to-many association which arises from the fact that it is represented by this "reverse" link from the contained object side of the association:
  1. From a Java point of view, we could potentially have two different Lecturer objects, both of which have the same Student object in their container. However, this is not possible for a one-to-many association because, in the database, each Student row refers to the single Lecturer row which contains it. If you want the true Java semantics, you have to represent the association as a many-to-many one.
  2. You cannot have the same object multiple times in the same collection. This is obvious when the collection property is of type Set, but one could use other types, such as List. However, the implementation of association by the reverse foreign key makes this impossible. Again, a many-to-many association can provide the appropriate semantics

Finally, there is the question of why one-to-many associations between entities cause problems. Consider the following code:
tx = session.beginTransaction();
Lecturer lect = new Lecturer("Gordon Brown") ;
lect.getAdvisees().add(new Student("Tony Blair")) ;
lect.getAdvisees().add(new Student("Michael Howard")) ;
session.save(lect);
tx.commit();

Note that the association belongs to the Lecturer class (as it is defined in Lecturer's mapping file). This means that adding a student to a lecturer's advisees is considered an operation on a lecturer, not on a student. Thus the SQL statements that would be generated for the above statements would include an insert for the lecturer object, together with an insert each for the two connected student objects (because the students are referred to by the lecturer and we have put the cascade="save-update" declaration in the Lecturer's mapping file. But because the association does not belong to the students, the saving of the students would not set the foreign key value to the advising lecture. Thus there would be two extra update statements for adding the lecturer's foreign key value into the student records. These extra two update statements are not just an efficiency problem: If every student should have a supervisor, then we would like to add the not-null="true" attribute to the key element in the mapping file for the association. However this would cause errors as the above sequence of inserts and updates does insert nulls (if only to immediately update them) where they should never occur.

The solution is to only create such one-to-many associations as the inverse end of a bidirectional many-to-one association. This gives ownership of the association to the Student end and, as we see below, leads to the foreign key being created as part of the initial insert of the Student record instead of after it as a consequence of the Lecturer insert.

Many-to-one, bidirectional Associations

In this case we allow navigation in both directions between the two classes. On the Many side it is a standard java reference. on the One side it is a collection. However, the two associations are not independent of each other but rather, one is the inverse of the other and the same foreign key column is used for both associations. Thus our Lecturer object now has a property which is a collection of Student objects while the Student objects have a properties which refers to the Student's supervising Lecturer.

A Many-to-One, bidirectional Association

To achieve this, we start by using the many-to-one element as before in the mapping file for the Student class, and the Set element as before in the mapping file for the Lecturer class, ensuring that both associations use the same column in Student's table to encode the association. Then we add a new attribute, inverse="true" to the set element in Lecturer's mapping file. Without this, adding a new Student as an advisee to a Lecturer would trigger Hibernate to set the foreign key column of the Student table twice: once for each association that has been changed. The inverse attribute tells hibernate that Student owns the association and that Hibernate should not trigger updates of the foreign key column when it changes on the Lecturer side.
Thus the mapping file for Lecturer looks like this:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="Lecturer" table="lecturers">
<id name="id" column="lecturer_id">
<generator class="sequence"/>
</id>
<version name="version" column="version"/>
<property name="name" column="name"/>
<set name="advisees" inverse="true" cascade="save-update" lazy="true">
<key column="lecturer_id"/>
<one-to-many class="Student"/>
</set>
</class>
</hibernate-mapping>

The mapping file for student is as follows:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="Student" table="students">
<id name="id" column="student_id">
<generator class="sequence"/>
</id>
<version name="version" column="version"/>
<property name="name" column="name"/>
<property name="regNo" column="reg_no"/>
<many-to-one name="advisor" column="lecturer_id" cascade="save-update"/>
</class>
</hibernate-mapping>

Now all the programmer has to do is to ensure that, when the Lecturer's advisee property property is changed, the corresponding correct changes are made to the appropriate Student's advisor property. So long as both are done together, the Java object graph will be correct and the correct update on disk will be made as well. Furthermore, since the association belongs to the Student, there will never be an insert of a Student record with a null Lecturer foreign key if the Student has an advisor, thus avoiding not-null constraint breaking. To ensure that these updates are made together, it is usual to add some convenience methods: in Lecturer, change the getAdvisees() and setAdvisees() methods to private and add a convenience method to update the object graph correctly when adding a new Student advisee to a Lecturer:

public void addAdvisee(Student st)
{
Lecturer oldAdvisor = st.getAdvisor() ;
if (oldAdvisor != this)
{
if (oldAdvisor != null)
oldAdvisor.getAdvisees().remove(st) ;
st.setAdvisor(this);
advisees.add(st) ;
}
}

Note how we are careful to correctly handle removal of a Student from a previous advising Lecturer before adding it to this one. Whether you need to do something similar for your code will depend on your detailed design.
If we have a true composition relationship, i.e., a parent-child relationship where if the parent gets deleted then the child should also be deleted etc., then we should change the cascade attribute on the set element in the Lecturer mapping file to be all-delete-orphan.

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".

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.