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:
Is there a way t have the #JPA mapping file outside the jar file. Looking f a solution to modifying t file w/o updating the jar file #spring
— Flemming Harms (@fnharms) October 12, 2016
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 theMETA-INF
directory in the root of the persistence unit or in theMETA-INF
directory of any jar file referenced by thepersistence.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.
