The minimal configuration for testing Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

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.

FREE EBOOK

4 Comments on “The minimal configuration for testing Hibernate

  1. Is the minimal configuration concept described here still up-to-date for Hibernate 5?

    From the Git one can see that it is more based on Hibernate 4.x.x, e.g:

    Use of:
    org.hibernate.cfg.Configuration#buildSessionFactory(org.hibernate.service.ServiceRegistry);
    In Hibernate 5.x.x the functionality is already implemented in parameterless method:
    org.hibernate.cfg.Configuration#buildSessionFactory();
    Furthermore, either EntityManager or Session is accessed separately and transactional operations are performed accordingly.
    Meanwhile in Hibernate 5.x.x this clear distinction between EntityManager and Session is no longer necessary, because the Ssession is inherited from EntityManager.
    Also the factory classes are now linked by inheritance, so that one can create the SessionFactory with all necessary configurations per configuration file or programmatically or both and from it by calling:
    javax.persistence.EntityManagerFactory#createEntityManager();
    on the created SessionFactory directly obtain the EntityManager.

    Just trying to understand the whole stuff and create an up-to-date one minimal Hibernate 5.x.x configuration for the tests.

    Thanks in advance!

    • I updated the entire article to match the latest base class I use for testing.

  2. How do you verify that your record is actually in the database?

    Do you:
    1. clear your session
    2. session.get(pk)
    3. assert everything?

    Or do you start a new transaction?

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.