How to use external XML mappings files (outside of JAR) with JPA and 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

Flemming Harms has asked a very good question on Twitter:

Basically, we want to move the JPA XML mappings outside of the application JAR so that we can change the mapping without affecting the jar file.

The JPA 2.1 Specification

The JPA specification is pretty clear about the location of the associated persistence.xml and XML mappings files (e.g. orm.xml):

An object/relational mapping XML file named orm.xml may be specified in the META-INF directory in the root of the persistence unit or in the META-INF directory of any jar file referenced by the persistence.xml.

Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the classpath.

So, the XML mappings files are supposed to be located on the classpath, so that the Classloader can load them as resources.

Therefore, the XML mappings can be located outside of a JAR file, but the containing folder must be included in the Java class path.

The Hibernate way

When using Hibernate, you don’t even need to include the XML mappings folder in the Java classpath because Hibernate can resolve any valid mapping-file URL.

Therefore, our persistence.xml mappings looks as follows:

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">

    <persistence-unit name="externalMapping" transaction-type="RESOURCE_LOCAL">

        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <mapping-file>file:///D:/Vlad/Work/Examples/mappings/orm.xml</mapping-file>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
            <property name="hibernate.connection.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1"/>
            <property name="hibernate.connection.username" value="sa"/>

            <property name="hibernate.connection.pool_size" value="5"/>

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>

            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>

    </persistence-unit>
    
</persistence>

The mapping-file can take any URL. In this particular example, the mappings folder is located outside of the directory where the application code resides.

Considering we have the following Post entity:

public class Post {

    private Long id;

    private String title;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

and its associated orm.xml mapping file:

<?xml version="1.0" encoding="UTF-8"?>

<entity-mappings 
    xmlns="http://java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
        http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
    version="2.0">

    <entity class="com.vladmihalcea.forum.Post"
            access="FIELD" name="Post">
        <attributes>
            <id name="id"/>
            <basic name="title"/>
        </attributes>
    </entity>

</entity-mappings>

When executing the following test case:

public class PostTest {

    protected final Logger LOGGER = 
        LoggerFactory.getLogger( getClass());

    private EntityManagerFactory entityManagerFactory;

    @Before
    public void setup() {
        entityManagerFactory = Persistence
            .createEntityManagerFactory( "externalMapping");
    }

    @After
    public void tearDown() {
        if ( entityManagerFactory != null && 
            entityManagerFactory.isOpen()) {
            entityManagerFactory.close();
        }
    }

    public EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }

    @Test
    public void HHH10385Test() throws Exception {
        doInJPA( this::getEntityManagerFactory, entityManager -> {
            Post post = new Post();
            post.setId(1L);
            post.setTitle("High-Performance Java Persistence");
            entityManager.persist(post);
        });

        doInJPA( this::getEntityManagerFactory, entityManager -> {
            Post post = entityManager.find(Post.class, 1L);
            LOGGER.debug("Fetched post: {}", post.getTitle());
        });
    }
}

The following outcome is obtained:

INSERT INTO Post 
    (title , id)
VALUES 
    ('High-Performance Java Persistence', 1)

SELECT
    p.id as id1_1_0_,
    p.title as title2_1_0_ 
FROM
    Post p 
WHERE
    p.id = 1

-- Fetched post: High-Performance Java Persistence

Great!

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

While JPA demands the XML file mappings to be located in the Java classpath, Hibernate allows you to store the XML file mappings everywhere you want. As long as the mapping-file URL is accessible, everything will be just fine.

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.