Posted on November 12, 2012 by Alessio Stalla

You're probably familiar with at least one method for configuring Hibernate, either using XML mappings or annotations. In this post, however, we're going to examine a method that you're unlikely to know: building Hibernate configuration metadata programmatically.

To be clear, we're not talking about programmatic configuration as explained in the Hibernate reference, which is limited to dynamically adding mapping files or annotated classes and setting Hibernate properties. We're talking about making your hands dirty with internal Hibernate configuration classes!

Digging into the Hibernate internals can be a helpful exercise to better understand how Hibernate models database tables and persistent Java entities. In this article, we're going to explore the programmatic construction of Hibernate mappings as implemented in Portofino 4. While not necessarily exhaustive, this analysis can be useful as an in-depth introduction to the Hibernate internals, which are, as far as we know, undocumented. Note: at the time of writing, in Portofino we use Hibernate 3.6.9, but the code presented here works with Hibernate at least up to version 4.1.8, the latest version available on Maven central right now.

The data model

Hibernate is a Mapper between the Object-Oriented world and the Relational world (hence the ORM acronym); it's not surprising, then, that its configuration data model is split between a “physical” model that describes the relational database, and a “logical” model that describes mapped Java entities. Both models are implemented with classes lying in the package org.hibernate.mapping.

The physical model deals with Tables, Columns, Primary and Foreign Keys, etc. It imposes very few restrictions on the structure of the database; the most important one being that each table is required to have a primary key, or Hibernate won't know how to map it.

The logical model, instead, deals with Classes, Properties and Collections, although, as we will see later, Hibernate knows how to map relational entities not only to Java beans, but to other data structures as well. The logical model is basically a Decorator for the physical model: generally, each logical object refers directly or indirectly to one or more corresponding physical objects (e.g. each Class wraps a Table). That said, the separation between the two models is not strict; the object graph is very interconnected, and it's not infrequent that an object representing a physical entity holds a reference to a logical entity.

The two models are ultimately connected by a single container object, an instance of the class org.hibernate.cfg.Mappings, as apparent from this high-level view of the steps involved in creating a SessionFactory, taken from Portofino:

Configuration configuration = new Configuration();

setupConnection(configuration);
setupConfigurationProperties(configuration);

Mappings mappings = configuration.createMappings();
//Class Mapping
classMapping(database, mappings);
//One2Many Mapping
o2mMapping(database, configuration, mappings);
//Many2One Mapping 
m2oMapping(database, configuration, mappings);

The physical model: tables and columns

The physical model mirrors the structure of the database. At the root of the model is a set of Table objects. Each Table is an aggregation of relevant database objects – at the time of writing, Portofino only knows about Columns, Primary Keys and Foreign Keys, but the Hibernate model is able to record information about Indexes and Constraints, too.

The following is the procedure that we use to configure a table:

  1. Create a Table from the Mappings:

    Table table = mappings.addTable(schemaName, null, tableName, null, false);

    The parameters of the addTable method are schema, catalog, name, subselect, abstract, but we only pass schema and name. Subselect is used to define logical tables (much like views, but not stored in the database). Abstract is used to define abstract classes, that do not correspond to any database table.

  2. Create the primary key. This is a relatively complex operation that crosses the boundaries between the physical and logical models, and we'll examine it in detail later.

  3. Create the columns:

    Column col = new Column();
    col.setName(name);
    col.setLength(len);
    col.setPrecision(len);
    col.setScale(scale);
    col.setNullable(nullable);
    col.setSqlTypeCode(jdbcType);
    col.setSqlType(columnType);
    

    Again, columns that are part of the primary key need special attention, but we'll deal with this later.

  4. Add them to the table and to the mappings object:

    table.addColumn(col);
    mappings.addColumnBinding(columnName, column, table);

Having dealt with the physical model, we're ready to address the other half of the world...

The logical model: classes and properties

The logical model deals with classes and properties. “Classes” are to be intended in a generic way, as we're not necessarily referring to Java classes (although that's the most common use of Hibernate). In fact, out of the box Hibernate can map to:

  • Java classes
  • Java Maps
  • DOM4J nodes (XML)

In Portofino 4, we use Java maps by default, as they require minimal setup and are still accessible with friendly syntax in Groovy. You can provide your own Java classes if you want, and even mix the two approaches.

Classes in Hibernate are modeled as instances of PersistentClass. This abstract class has several subclasses, but in this article we only deal with one of them, RootClass, which you'll use most of the time. The other subclasses of PersistentClass are needed to handle different types of inheritance in your data model.

This is the code we use in Portofino to configure a RootClass:

  1. Create it and set its basic properties:

    RootClass clazz = new RootClass();
    clazz.setEntityName(entityName);
    clazz.setJpaEntityName(entityName);
    if (javaClass != null) {
        clazz.setClassName(javaClass);
        clazz.setProxyInterfaceName(javaClass);
    }
    clazz.setLazy(lazy);
    clazz.setTable(table);

    As you can see, we only map to a Java class if a javaClass attribute is provided. Otherwise we map to HashMaps.

  2. Add a property for each column:

    Property prop = new Property();
    prop.setName(propertyName);
    clazz.addProperty(prop);
  3. Add the class to the Mappings:

    mappings.addClass(clazz);
    mappings.addImport(clazz.getEntityName(), clazz.getEntityName());

Mapping to HashMaps is more dynamic, while mapping to Java classes is more strongly typed. Which approach to use is up to you.

Bridging between the two models: values

So far, we have seen the basic building blocks of the Hibernate model, but we have skipped a few important steps. For example, how are Columns and Properties connected? Through an abstraction known as a Value, which is the subject of this section.

A Value is anything that can be persisted as the value of a column, including scalar values (numbers, strings, dates, etc.) and collections (bags, sets, lists, etc.). Let's concentrate on scalar values for now: we'll only deal with an implementation of the Value interface – SimpleValue.

These are the steps we take to create a SimpleValue:

SimpleValue value = new SimpleValue(mappings, tab);
value.setTypeName(typeName);
if(typeParams != null) {
    value.setTypeParameters(typeParams);
}
value.addColumn(col);
prop.setValue(value);

As you can see from the addColumn() invocation, the value has a list of columns (although generally the column is just one) and is contained in a Property. It acts as a bridge between the logical model and the physical model.

Simple values have another fundamental role: they hold information regarding the (Hibernate) data type of the associated property. Portofino uses some heuristics to determine the correct type from database metadata, while still allowing the user to customize the mapping, but those heuristics are not shown here.

typeName is the name of a Hibernate type (a derivative of org.hibernate.type.Type, e.g., org.hibernate.type.IntegerType). A Hibernate type is a bridge between a JDBC type and a Java type.

Next: keys and relationships

We'll follow up with a second article that will deal with primary keys and relationships - the key information we mentioned in this article but have not yet covered. Stay tuned!

 
 

comments powered by Disqus