How to get access to database table metadata with Hibernate 5
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
One of my readers has recently asked me to answer this StackOverflow question, and because the question is very interesting, I decided to turn the answer into a blog post.
In this article, I’m going to show you how you can get access to database table metadata using Hibernate 5 API.
How to get access to database table metadata with Hibernate 5@vlad_mihalceahttps://t.co/J8OxBFA0Ql pic.twitter.com/lOQgRM1H51
— Java (@java) August 14, 2019
Integrator
Hibernate is very flexible, so it defines many SPI (Service Provider Interfaces) that you can register to customize Hibernate internals. One of these interfaces is org.hibernate.integrator.spi.Integrator
which is used by many technologies that integrate with Hibernate ORM, like Bean Validation, Envers or JACC Security Provider.
Using the Hibernate Integrator API, we can write our own component that captures the SessionFactory
build-time metadata which, otherwise, is only available during bootstrap.
public class MetadataExtractorIntegrator implements org.hibernate.integrator.spi.Integrator { public static final MetadataExtractorIntegrator INSTANCE = new MetadataExtractorIntegrator(); private Database database; public Database getDatabase() { return database; } @Override public void integrate( Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { database = metadata.getDatabase(); } @Override public void disintegrate( SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { } }
The org.hibernate.boot.model.relational.Database
is what we are interested in since it contains all the database-related metadata.
To register MetadataExtractorIntegrator
with Hibernate we have two possibilities based on the bootstrap method.
Hibernate-native boostrap
If you’re using the Hibernate-native bootstrap, then you can register the Integrator
with the BootstrapServiceRegistryBuilder
as follows:
final BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder() .enableAutoClose() .applyIntegrator(MetadataExtractorIntegrator.INSTANCE) .build(); final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder(bootstrapServiceRegistry) .applySettings(properties()) .build();
JPA boostrap
If you’re using the JPA bootstrap, then you can register the Integrator
with the BootstrapServiceRegistryBuilder
as follows:
Map<String, Object> configuration = new HashMap<>(); Integrator integrator = integrator(); if (integrator != null) { configuration.put("hibernate.integrator_provider", (IntegratorProvider) () -> Collections.singletonList( MetadataExtractorIntegrator.INSTANCE ) ); } EntityManagerFactory entityManagerFactory = new EntityManagerFactoryBuilderImpl( new PersistenceUnitInfoDescriptor(persistenceUnitInfo), configuration ) .build();
To see how you can set the
hibernate.integrator_provider
configuration property when using Spring with JPA or Spring with Hibernate, check out this article.
Domain Model
Assuming we have the following database tables mapped by our JPA application:
When running the following test case:
for(Namespace namespace : MetadataExtractorIntegrator.INSTANCE .getDatabase() .getNamespaces()) { for( Table table : namespace.getTables()) { LOGGER.info( "Table {} has the following columns: {}", table, StreamSupport.stream( Spliterators.spliteratorUnknownSize( table.getColumnIterator(), Spliterator.ORDERED ), false ) .collect( Collectors.toList()) ); } }
Hibernate generates the following output:
Table org.hibernate.mapping.Table(post) has the following columns: [ org.hibernate.mapping.Column(id), org.hibernate.mapping.Column(title), org.hibernate.mapping.Column(version) ] Table org.hibernate.mapping.Table(post_comment) has the following columns: [ org.hibernate.mapping.Column(id), org.hibernate.mapping.Column(review), org.hibernate.mapping.Column(version), org.hibernate.mapping.Column(post_id) ] Table org.hibernate.mapping.Table(post_details) has the following columns: [ org.hibernate.mapping.Column(id), org.hibernate.mapping.Column(created_by), org.hibernate.mapping.Column(created_on), org.hibernate.mapping.Column(version) ] Table org.hibernate.mapping.Table(post_tag) has the following columns: [ org.hibernate.mapping.Column(post_id), org.hibernate.mapping.Column(tag_id) ] Table org.hibernate.mapping.Table(tag) has the following columns: [ org.hibernate.mapping.Column(id), org.hibernate.mapping.Column(name), org.hibernate.mapping.Column(version) ]
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Hibernate is highly customizable, and the Integrator
SPI allows you to get access to the Database
metadata which you can later inspect from your enterprise application.
