Enhancing Hibernate’s metadata model

Hibernate internally uses an abstract metadata model to validate the data model defined by the declared entities. The framework also offers entry points to enhance this metadata model with custom code.

In this article we show how to extend the generated metadata model using annotations. Our example is to implement a convenient way to define common table prefixes for subclasses of a “@MappedSuperclass” entity.

Meta data enhancement

Imagine a deeply nested data model where you want to use a common prefix for all tables of a given mapped super class. You could of course repeatedly specify the complete name for every entity. Here we want to declare this prefix just once using a custom annotation:

@MappedSuperclass
@MappedSuperclassTable(prefix = "MY_PREFIX_")
public class AbstractSuperThing
{ ... }

In the sub classes we just use the @Table annotation as usual.

@Entity
@Table(name = "TEST1")
public class MyEntity extends AbstractSuperThing
{ ... }

To generate the physical table name "MY_PREFIX_TEST1" in the data base for these entities we implement an MetadataEnhancer class with the following method:

public void addEntityTableMetadata(final Metadata metadata)
{
  for (PersistentClass entityBinding : metadata.getEntityBindings())
  {
    Table table = entityBinding.getTable();
    String tableName = table.getName();
    String tableNamePrefix = determineTableNamePrefix(entityBinding);
    ...
    table.setName(tableNamePrefix + tableName);
  }
}

Integrate using Hibernate SPI

At startup, Hibernate first validates the meta model for inconsistencies. Afterwards the meta model is used, e.g., to generate SQL queries.
For some reason however, Hibernate generates the metadata twice for each of these phases. Furthermore the framework uses different program codes to create the metadata model. Thus we have to apply our modifications also twice, and have to use two different entry points to do so.

For the validation phase we use a MetadataBuilderImplementor, for the latter a SessionFactoryBuilderImplementor. Both of which will be created by factories specified in META-INF/services/org.hibernate.boot.spi.MetadataBuilderFactory and META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory, respectively.

MetadataBuilderFactory

public class My_MetadataBuilderFactory implements MetadataBuilderFactory
{
  public MetadataBuilderImplementor getMetadataBuilder(
     MetadataSources metadatasources,
     MetadataBuilderImplementor defaultBuilder)
  {
    return new My_MetadataBuilderImplementor(defaultBuilder);
  }
}

public class My_MetadataBuilderImplementor
    implements MetadataBuilderImplementor
{
  ...
  public Metadata build()
  {
    final Metadata metadata = super.build();

    this.enhancer.addEntityTableMetadata(metadata);

    return metadata;
  }
}

SessionFactoryBuilderFactory

public class My_SessionFactoryBuilder
  implements SessionFactoryBuilderFactory
{
  public SessionFactoryBuilder getSessionFactoryBuilder(
    MetadataImplementor metadata,
    SessionFactoryBuilderImplementor defaultBuilder)
  {
    final My_SessionFactoryBuilderImplementor implementor =
      new My_SessionFactoryBuilderImplementor(metadata, defaultBuilder);
    implementor.enhance();
    return implementor;
  }
}

public class My_SessionFactoryBuilderImplementor
  extends AbstractDelegatingSessionFactoryBuilderImplementor
{
  ...
  public void enhance()
  {
    this.enhancer.addEntityTableMetadata(this.metadata);
  }
}

At runtime, the concatenated table name is then used as well for schema generation/validation as for creating SQL queries.

markus.dahm@akquinet.de

Posted in All

Leave a Reply