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.

Seize the deal! 40% discount. Seize the deal! 40% discount. Seize the deal! 40% discount.

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.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.