For optimal performance developers often define database indexes in the JPA data model classes. While this is relatively easy at entity class level, it becomes quite cumbersome if you your model consists of a deeply nested inheritance hierarchy using “mapped” super classes. I.e., there is no direct way that indexes defined for super classes will be added to entities automatically. Instead you have to repeat them every time.
In this article we show how to to circumvent this problem using Hibernate’s MetadataBuilderImplementor
SPI.
The solution is two-fold: First we create an annotation to define the indexes at class and property level. Secondly we add a MetadataBuilderImplementor
implementation, declared in META-INF/services/org.hibernate.boot.spi.MetadataBuilderFactory
.
Annotation
The annotation is very straightforward to implement, re-using the JPA’s existing @Index annotation:
@Target({ FIELD, TYPE })
@Retention(RUNTIME)
public @interface Indexes
{
Index[] value() default {};
}
This annotation may be used in your model classes like this:
@MappedSuperclass
@Indexes({
@Index(columnList = COLUMN_STATE),
@Index(columnList = COLUMN_ADDRESS_STREET)
})
public abstract class AbstractEntity
{
@Enumerated(EnumType.STRING)
@ElementCollection
@Indexes({
@Index(name = "IDX_VALUE", columnList = VALUE_COLUMN,
@Index(name = "IDX_JOIN", columnList = VALUE_COLUMN + "," + PARENT_COLUMN, unique = true)
})
private final Set<GisRevier> gisReviere = new HashSet<>();
@Column(name = COLUMN_STATE)
private String state;
...
}
MetadataBuilderImplementor
Our implementation enriches the metadata model created by Hibernate with the additional information provided by our annotation.
public class MyMetadataBuilderImplementor extends AbstractDelegatingMetadataBuilderImplementor<MyMetadataBuilderImplementor>
{
public Metadata build()
{
final Metadata metadata = super.build();
addEntityTableIndexMetadata(metadata);
return metadata;
}
...
We then check all entities and mapped super classes for our annotation, ascending in the class hierarchy of each visited entity:
private void addEntityTableIndexMetadata(final Metadata metadata)
{
for (PersistentClass entityBinding : metadata.getEntityBindings())
{
final List<Index> indexes = extractIndexes(entityBinding);
final Table table = entityBinding.getTable();
addColumnIndexes(indexes, table);
}
}
Finally, we add the information to the meta model:
private void addColumnIndexes(final List<Index> indexes, final Table table)
{
for (Index index : indexes)
{
final List<String> columnNames =
Arrays.stream(index.columnList().split(","))
.map(String::trim).collect(Collectors.toList());
final String indexName = getIndexName(index, columnNames);
final org.hibernate.mapping.Index hibIndex =
table.getOrCreateIndex(indexName);
final List<Column> columns = columnNames.stream()
.map(Identifier::toIdentifier).map(table::getColumn)
.peek(column -> Objects.requireNonNull(column, "Cannot find column in " + columnNames + " of class " + table.getName()))
.collect(Collectors.toList());
columns.forEach(hibIndex::addColumn);
if (index.unique())
{
final UniqueKey uniqueKey =
table.getOrCreateUniqueKey(getIndexName(index, columnNames));
columns.forEach(uniqueKey::addColumn);
}
}
}
Afterwards everything is managed automatically by Hibernate, in particular the creation of database indexes and constraints.
markus.dahm@akquinet.de