The minimal configuration for testing Hibernate
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
In my previous post I announced my intention of creating a personal Hibernate course. The first thing to start with is a minimal testing configuration.
You only need Hibernate
In a real production environment you won’t use Hibernate alone, as you may integrate it in a Java EE or Spring container. For testing Hibernate features you don’t need a full-blown framework stack, you can simply rely on Hibernate flexible configuration options.
Test case configuration
The first thing to build is either the JPA EntityManagerFactory
or the Hibernate SessionFactory
:
@Before public void init() { if(nativeHibernateSessionFactoryBootstrap()) { sf = newSessionFactory(); } else { emf = newEntityManagerFactory(); } } @After public void destroy() { if(nativeHibernateSessionFactoryBootstrap()) { if (sf != null) { sf.close(); } } else { if (emf != null) { emf.close(); } } } public EntityManagerFactory entityManagerFactory() { return nativeHibernateSessionFactoryBootstrap() ? sf : emf; }
Bootstrapping JPA
To bootstrap JPA for testing, it’s easier if you build the JPA environment programmatically, rather than using a persistence.xml
configuration file.
protected EntityManagerFactory newEntityManagerFactory() { PersistenceUnitInfo persistenceUnitInfo = persistenceUnitInfo( getClass().getSimpleName() ); Map<String, Object> configuration = new HashMap<>(); configuration.put( AvailableSettings.INTERCEPTOR, interceptor() ); Integrator integrator = integrator(); if (integrator != null) { configuration.put( "hibernate.integrator_provider", (IntegratorProvider) () -> Collections.singletonList(integrator) ); } return new EntityManagerFactoryBuilderImpl( new PersistenceUnitInfoDescriptor( persistenceUnitInfo ), configuration ).build(); }
The PersistenceUnitInfo
is created like this:
protected PersistenceUnitInfoImpl persistenceUnitInfo( String name) { PersistenceUnitInfoImpl persistenceUnitInfo = new PersistenceUnitInfoImpl( name, entityClassNames(), properties() ); String[] resources = resources(); if (resources != null) { persistenceUnitInfo .getMappingFileNames() .addAll(Arrays.asList(resources)); } return persistenceUnitInfo; } protected List<String> entityClassNames() { return Arrays.asList( entities() ).stream() .map(Class::getName) .collect( Collectors.toList() ); }
Bootstrapping Hibernate natively
If we want to bootstrap Hibernate natively, we can do it like this:
private SessionFactory newSessionFactory() { final BootstrapServiceRegistryBuilder bsrb = new BootstrapServiceRegistryBuilder() .enableAutoClose(); Integrator integrator = integrator(); if (integrator != null) { bsrb.applyIntegrator( integrator ); } final BootstrapServiceRegistry bsr = bsrb.build(); final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder(bsr) .applySettings(properties()) .build(); final MetadataSources metadataSources = new MetadataSources(serviceRegistry); for (Class annotatedClass : entities()) { metadataSources.addAnnotatedClass( annotatedClass ); } String[] packages = packages(); if (packages != null) { for (String annotatedPackage : packages) { metadataSources.addPackage( annotatedPackage ); } } String[] resources = resources(); if (resources != null) { for (String resource : resources) { metadataSources.addResource(resource); } } final MetadataBuilder metadataBuilder = metadataSources .getMetadataBuilder(); metadataBuilder .enableNewIdentifierGeneratorSupport(true); metadataBuilder .applyImplicitNamingStrategy( ImplicitNamingStrategyLegacyJpaImpl.INSTANCE ); final List<Type> additionalTypes = additionalTypes(); if (additionalTypes != null) { additionalTypes.stream().forEach(type -> { metadataBuilder.applyTypes( (typeContributions, sr) -> { if(type instanceof BasicType) { typeContributions.contributeType( (BasicType) type ); } else if (type instanceof UserType ){ typeContributions.contributeType( (UserType) type ); } else if (type instanceof CompositeUserType) { typeContributions.contributeType( (CompositeUserType) type ); } }); }); } additionalMetadata(metadataBuilder); MetadataImplementor metadata = (MetadataImplementor) metadataBuilder .build(); final SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder(); Interceptor interceptor = interceptor(); if(interceptor != null) { sfb.applyInterceptor(interceptor); } return sfb.build(); }
Defining entities
Entities can be defined as nested classes, like this:
@Entity(name = "Post") @Table(name = "post") public static class Post { @Id @GeneratedValue private Long id; private String title; //Getters and setters omitted for brevity } @Entity(name = "PostComment") @Table(name = "post_comment") public static class PostComment { @Id @GeneratedValue private Long id; private String review; @ManyToOne(fetch = FetchType.LAZY) private Post post; //Getters and setters omitted for brevity }
Notice that we explicitly defined the name
attribute in the @Entity
annotation, as otherwise, Hibernate would use the TestClass$EntityClass
entity name.
Now, we just need to extend the entities
method and provide the entity classes we want to be registered when bootstrapping JPA or Hibernate:
@Override protected Class<?>[] entities() { return new Class<?>[] { Post.class, PostComment.class, }; }
Testing time
Now, we can just define a test method like this one and everything will work like a charm:
@Test public void testPersistAndQuery() { Post post = new Post(); post.setTitle( "High-Performance Java Persistence" ); PostComment comment = new PostComment(); comment.setReview( "Amazing book!" ); comment.setPost(post); doInJPA(entityManager -> { entityManager.persist(post); entityManager.persist(comment); }); doInJPA(entityManager -> { PostComment postComment = entityManager .createQuery( "select pc " + "from PostComment pc " + "join fetch pc.post " + "where pc.id = :id", PostComment.class) .setParameter("id", comment.getId()) .getSingleResult(); assertEquals( "High-Performance Java Persistence", postComment.getPost().getTitle() ); assertEquals( "Amazing book!", postComment.getReview() ); }); }
The doInJPA
method encapsulates the logic for opening an EntityManager
, starting an EntityTransaction
, executing the Lambda, committing or rolling back the transaction and finally closing the EntityManager
. You can find the doInJPA
method in this AbstractTest
class.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
This is the minimal configuration set-up we need for testing Hibernate features. I use it whenever I want to test a certain Hibernate feature.
Code available on GitHub.
