How to bootstrap Hibernate without the persistence.xml file

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!

Why?

JPA relies heavily on the persistence.xml configuration file, and the standard API to bootstrap a JPA provider programmatically requires too much boilerplate code. While in a typical enterprise application, providing a persistence.xml file is not really an issue, this requirement doesn’t get along with unit testing, especially when tests are completely isolated and they need to validate different aspects of JPA or Hibernate.

That was an issue that I bumped into when writing test cases for the High-Performance Java Persistence book. All my tests need to be isolated, and not all of them share the same settings or entities.

In my case, using a single persistence.xml file was definitely out of the question because any change would have a rippling effect throughout the whole test suite.

Hibernate to the rescue

Hibernate is awesome. It allows you do build an EntityManagerFactory completely programmatically and with few lines of code:

protected EntityManagerFactory newEntityManagerFactory() {
    PersistenceUnitInfo persistenceUnitInfo = 
        persistenceUnitInfo(getClass().getSimpleName());
    Map<String, Object> configuration = new HashMap<>();
    configuration.put(AvailableSettings.INTERCEPTOR, 
        interceptor()
    );

    return new EntityManagerFactoryBuilderImpl(
            new PersistenceUnitInfoDescriptor(
                persistenceUnitInfo), configuration
    ).build();
}

protected PersistenceUnitInfoImpl persistenceUnitInfo(
    String name) {
    return new PersistenceUnitInfoImpl(
        name, entityClassNames(), properties()
    );
}

Each test starts with some reasonable default properties, and entities must be provided on a per-test basis.

protected Properties properties() {
    Properties properties = new Properties();
    properties.put("hibernate.dialect", 
        dataSourceProvider().hibernateDialect()
    );
    properties.put("hibernate.hbm2ddl.auto", 
        "create-drop");
    DataSource dataSource = newDataSource();
    if (dataSource != null) {
        properties.put("hibernate.connection.datasource", 
        dataSource);
    }
    return properties;
}

protected List entityClassNames() {
    return Arrays.asList(entities())
        .stream()
        .map(Class::getName)
        .collect(Collectors.toList());
}

protected abstract Class[] entities();

A test can define its own settings and entities, and, this way, we can encapsulate the entire environment.

@Override
protected Class<?>[] entities() {
    return new Class<?>[] {
        Patch.class
    };
}

@Entity(name = "Patch")
public class Patch {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ElementCollection
    @CollectionTable(
            name="patch_change",
            joinColumns=@JoinColumn(name="patch_id")
    )
    @OrderColumn(name = "index_id")
    private List<Change> changes = new ArrayList<>();

    public List<Change> getChanges() {
        return changes;
    }
}

@Embeddable
public class Change {

    @Column(name = "path", nullable = false)
    private String path;

    @Column(name = "diff", nullable = false)
    private String diff;

    public Change() {
    }

    public Change(String path, String diff) {
        this.path = path;
        this.diff = diff;
    }

    public String getPath() {
        return path;
    }

    public String getDiff() {
        return diff;
    }
}
I'm running an online workshop on the 11th of October about High-Performance SQL.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

This technique is not something new. The Spring framework LocalContainerEntityManagerFactoryBean can also be configured without an actual persistence.xml file.

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.