Posted on March 12, 2013 by Alessio Stalla

In part 1 of this article, we looked at some aspects of the Hibernate data model. While useful to understand Hibernate better, part 1 wasn't sufficient for reaching our goal of building a working Hibernate session factory programmatically. We're now going to explore two important areas that we skipped last time: primary keys and relationships.

Primary keys and Identifiers

By "primary key", Hibernate refers to the physical side of the model; its logical counterpart is called an identifier, or ID. Recall that each table must have a primary key to be mapped by Hibernate. While often in modern applications synthetic keys are used everywhere, Hibernate can map composite keys as well, so that in most cases legacy schemas don't need adjustments.

The distinction between physical and logical models is valid for primary keys as well, but it's easier if we look at both sides at the same time. Primary keys are created like this:

PrimaryKey primaryKey = new PrimaryKey();
primaryKey.setName(pkName);
primaryKey.setTable(table);
table.setPrimaryKey(primaryKey);

How they are connected to the rest of the model varies depending on whether we're dealing with simple or composite keys.

Simple primary keys

A primary key is simple if it is made of a single column. In this case, we need to create a Column and a Property as we did for the other column mappings, and add that column to the PrimaryKey object (primaryKey.addColumn(col)). For convenience, in Portofino we create the Column and Property together with the primary key (and thus we skip them when creating regular columns), but how to organize your code is up to you. Furthermore, the property should be marked as read-only:

prop.setInsertable(false);
prop.setUpdateable(false);

The SimpleValue associated with the property must be given a generator strategy, i.e., a policy for generating values for the primary key. Some of the possible options are "assigned" (the value is provided by the user), "identity" (the column is auto-increment, for databases that support it), "enhanced-sequence" (values for the key are generated using a database sequence), "enhanced-table" (values for the key are generated using another table as a sequence generator). Look at the Hibernate documentation for a complete listing of the available options, and see the Portofino 4 source code for some examples of how to configure them programmatically. The simplest case is to leave the responsibility of providing the id to the application:

SimpleValue id = (SimpleValue) col.getValue();
id.setIdentifierGeneratorStrategy("assigned");

Finally, the primary key just created must be registered with its owning table and persistent class:

table.setIdentifierValue(id);
clazz.setIdentifier(id);
clazz.setIdentifierProperty(prop);

Composite primary keys

Composite primary keys, which are made of multiple columns and are most often found in legacy database schemas, are more complicated. To map them, Hibernate  requires an extra object holding all the properties that constitute the identifier. Often a dedicated class is used, as explained in the Hibernate documentation; however,  in Portofino, we reuse the persistent class itself as its own identifier. This choice is not universally applicable, but within our design it works well and covers a wide range of scenarios. In any case, to setup a composite key, first you must configure the persistent class to use an embedded identifier:

clazz.setEmbeddedIdentifier(true);

Then, you have to create a Component representing the composite identifier:

Component component = new Component(mappings, persistentClass);
component.setRoleName(someUniqueName + ".id");
component.setEmbedded(true);
component.setNodeName("id");
component.setKey(true);
component.setNullValue("undefined");

If the persistent class is mapped to a Java class, the component is not dynamic and you have to configure it like this:

component.setComponentClassName(javaClassFullyQualifiedName);

else, you have to mark the component as dynamic:

component.setDynamic(true);

For each primary key column, you need to have a Column and associated Property as usual, and in addition you must attach them to the PrimaryKey and Component, respectively:

primaryKey.addColumn(col);
component.addProperty(prop);

Finally, you have to register the primary key with the persistent class:

table.setIdentifierValue(component);
clazz.setIdentifier(component);
clazz.setDiscriminatorValue(someUniqueName);
table.setPrimaryKey(primaryKey);

Note that someUniqueName is the same value used earlier to set the role name of the component.

Relationships

Relationships are the defining characteristic of the relational model; they're arguably the aspect that is the most distant from the OO model, and the biggest challenge for an ORM to overcome.

Hibernate can map all kinds of relationships: one-to-one, one-to-many, many-to-one, many-to-many. In Portofino we restricted ourselves to mapping one-to-many relationships in both directions (that is, each relationship is mapped twice, as a collection on the “one” side, and as a non-collection property on the “many” side). This schema is powerful enough to model the other kinds of relationships, and we'll only talk about it in this article.

Furthermore, in Portofino we only use Bags – unordered collections that admit duplicates. Hibernate supports other kinds of collections such as sets (which do not contain duplicates) and lists (which have a well-defined ordering). It is trivial to replace the use of Bags with other collection types if you need to.

Each of the relationships we're considering involves two persistent classes; as we said, we're restricting ourselves to one-to-many relationships, so let's use the names classOne and classMany for clarity. Of course, it is possible to have classOne == classMany (self relationships). Similarly, we'll refer to oneEntityName and manyEntityName, etc. We'll use, instead, onePropertyName to refer to the property on the “many” side (i.e. in classMany) that holds a reference to the “one” object, and similarly manyPropertyName as the property on the “one” side that holds a collection of “many” objects.

The “one” side

On this side of the relationship we hold a collection of related entities, so the first step is to create such a collection (a Bag, as we saw earlier):

Bag coll = new Bag(mappings, classOne);
coll.setRole(oneEntityName + "." + manyPropertyName);
coll.setNodeName(onePropertyName);
coll.setCollectionTable(classMany.getTable());
OneToMany oneToMany = new OneToMany(mappings, coll.getOwner());
coll.setElement(oneToMany);
oneToMany.setReferencedEntityName(manyEntityName);
oneToMany.setAssociatedClass(classMany);
oneToMany.setEmbedded(true);
coll.setSorted(false);

You can control the laziness and the fetch mode of the collection by setting the relevant properties.

The second step is to assign a key to the collection.

DependantValue dv = ...;
List<Column> oneColumns = ...; //Relationship columns in tableOne
List<Column> manyColumns = ...; //Relationship columns in tableMany
tableMany.createForeignKey(
    relationship.getName(), manyColumns, oneEntityName, oneColumns);
dv.setNullable(false);
coll.setKey(dv);

We'll return later on the DependantValue that connects the collection with the key column(s) on the one-side table.

Once you have a properly configured collection, you need to register it and create a Property for it.

mappings.addCollection(set);
Property prop = new Property();
prop.setName(manyPropertyName);
prop.setNodeName(manyPropertyName);
prop.setValue(set);
classOne.addProperty(prop);

You can control the cascade policy by setting prop.cascade appropriately.

Let's see briefly how you could create the DependantValue that we mentioned earlier. This is another kind of Value whose type is determined by another value (for example, a foreign key is typed by the referenced primary key). As for primary keys, that value is configured differently if the key is single or composite.

If the key is simple, it refers to a single column and thus a single property; let's call the latter refProp. The DependantValue is simply created like this:

dv = new DependantValue(
    mappings, classMany.getTable(), refProp.getPersistentClass().getKey());
dv.setNullable(true);
dv.setUpdateable(true);
for(Column col : manyColumns) {
    dv.addColumn(col);
}

If the key is composite, just like the primary key case, you need an intermediate Component to model it.

Component component = new Component(mappings, collection);
component.setDynamic(manyEntityJavaClass == null);
component.setEmbedded(true);
dv = new DependantValue(mappings, classMany.getTable(), component);
for(Property refProp : oneProperties) {
    component.addProperty(refProp);
}
dv.setNullable(true);
dv.setUpdateable(true);
for(Column col : manyColumns) {
    dv.addColumn(col);
}

That's all.

The “many” side

On this side we hold a reference back to the “one” object, which is conceptually the owner of the relationship. The configuration in this case is easier than what we saw earlier. Let's keep using the same variable names as in the “one” case.

First, we create a ManyToOne:

ManyToOne m2o = new ManyToOne(mappings, tableMany);
m2o.setLazy(lazy);
m2o.setReferencedEntityName(oneEntityName);
m2o.createPropertyRefConstraints(Collections.singletonMap(oneEntityName, classOne));
for(Column col : manyColumns) {
    m2o.addColumn(col);
}

Then, we add a Property to the persistent class:

Property prop = new Property();
prop.setName(onePropertyName);
prop.setNodeName(onePropertyName);
prop.setValue(m2o);
prop.setCascade(...);
prop.setInsertable(false);
prop.setUpdateable(false);
classMany.addProperty(prop);

And we're done.

Conclusions

Starting from our concrete experience with Portofino, we gave an overview of the Hibernate mapping data model. While this is not a reference manual and we skipped some of the details, we hope that you'll be able to adapt what we've shown to your own use cases, and use it as the basis for further exploration. We felt that this topic, while not properly mainstream, was not adequately represented in existing documentation and articles.

 

comments powered by Disqus