How to use external XML mappings files (outside of JAR) with JPA and Hibernate

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 class path.

So, the XML mappings files are supposed to be located on the class path, 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 class path 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!

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

While JPA demands the XML file mappings to be located in the Java class path, 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.

Enter your email address to follow this blog and receive notifications of new posts by email.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s