|

Mapping Definitions in JPA and Hibernate – Annotations, XML or both?


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


If you’re using Hibernate for several years, you might remember the time when you had to define all your mappings in the orm.xml file. You had a bunch of Java classes that represented your domain model and a long XML file with the mapping and query information. One of the main challenges with that approach was that you had the code and the mapping information in two different files which you had to keep in sync.

All that started to change with the introduction of annotations in Java 5. JPA and Hibernate offered annotations to define the entity mappings and queries. The mapping definitions and queries became part of the entities. That puts all information into one place and makes it easier to understand. You also don’t have to keep multiple files in sync (which I think is an incredible benefit…).

2 valid options

OK, so now we have 2 options to define mappings and queries. And before you ask, I think that both of them are ok. I personally prefer annotations over XML. But there are enough projects out there which show that XML mappings are still a valid option. And splitting your domain model classes and mapping definitions can provide its own benefits. If you need to, you can even store the mapping definitions in a configuration file outside of the deployed jar file.

So, let’s take a more detailed look at both approaches. And after that, I want to show you how you can use both methods in the same project and store the XML configuration in an external file on the classpath.

Mapping definitions

Let’s begin with the mapping definitions. As I said, you can either define them via annotations or in an XML file.

Basic entity mapping

The following code snippet shows the simplest, annotation based mapping. You just add an @Entity annotation to the class and an @Id annotation to the primary key attribute. Hibernate maps the entity to a database table with the same name and uses a default mapping for each attribute.

@Entity
public class Author {

	@Id
	private Long id;

	…
}

You can define the same configuration with the following XML file.

<entity-mappings>
    <entity class="org.thoughts.on.java.model.Author" name="Author">        
        <attributes>
            <id name="id">
            </id>
        </attributes>
   </entity>
</entity-mappings>

Customized entity mapping

You can adapt this default mapping with a set of annotations. The following example tells Hibernate to map the Author entity to the author table in the bookstore schema, to map the id attribute to the author_id column and to use the sequence author_seq to generate its primary key value.

@Entity
@Table(name = “author”, schema = “bookstore”)
public class Author {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = “author_generator”)
	@SequenceGenerator(name=”author_generator”, sequenceName = “author_seq”)
	@Column(name = “author_id”)
	private Long id;

	…
}

And as in the previous example, you can, of course, define the same mapping in XML.

<entity-mappings>
    <entity class="org.thoughts.on.java.model.Author" name="Author">
    	<table name="author" />
    	<schema name="bookstore" />   
        <attributes>
            <id name="id">
            	<generated-value strategy="sequence" generator="author_generator"/>
            	<column name="author_id"/>
            </id>
        </attributes>
   </entity>
   <sequence-generator name="author_generator" sequence-name="author_seq"/>
</entity-mappings>

Query definitions

You can also define named queries, result set mappings, entity graphs, etc. via annotations and in XML.

Named queries

You can define a named JPQL query with a @NamedQuery and a named native SQL query with a @NamedNativeQuery annotation. Both of them follow the same idea. You define the query once and reference the definition by its name to instantiate it in your business code.

The following shows an example of a named JPQL query. You can learn more about native queries in my free ebook.

@Entity
@NamedQuery(name = Author.QUERY_SELECT_BY_ID, query = “SELECT a FROM Author a WHERE a.id = :” + Author.PARAM_ID)
public class Author {
	public static final String QUERY_SELECT_BY_ID = “Author.selectById”;
	public static final String PARAM_ID = “id”;

	…
}

And you can define the same query in your orm.xml file.

<entity-mappings>
  <entity class="org.thoughts.on.java.model.Author" name="Author">
    ...
    <named-query name="Author.selectById">
        <query><![CDATA[
        SELECT a FROM Author a WHERE a.id = :id
        ]]></query>
    </named-query>
  </entity>
  ...
</entity-mappings>

Result set mappings

Native SQL queries allow you to use all SQL features supported by your database but they return an Object[] or a List<Object[]> instead of the mapped objects you get from a JPQL query. You can either map these results programmatically or define a result set mapping and let Hibernate do the work. I will only show a quick example of such a mapping in this post. If you want to dive deeper, you should take a look at my Result Set Mapping series. The following code snippet shows a simple @SqlResultSetMapping definition that maps the columns authorId, firstName, lastName and version of the native query result to the attributes of the Author entity.

@SqlResultSetMapping(
        name = "AuthorMapping",
        entities = @EntityResult(
                entityClass = Author.class,
                fields = {
                    @FieldResult(name = "id", column = "authorId"),
                    @FieldResult(name = "name", column = "name")}))

If you don’t want to define this with a set of annotations, you can also do this with XML configuration.

<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings>
	<entity class="org.thoughts.on.java.model.Author" name="Author">
		...
    
		<sql-result-set-mapping name="AuthorMappingXml">
			<entity-result entity-class="org.thoughts.on.java.model.Author">
				<field-result name="id" column="authorId" />
				<field-result name="name" column="name" />
			</entity-result>
		</sql-result-set-mapping>
	</entity>
	...
</entity-mappings>

Entity Graphs

Entity Graphs define a graph of entities which Hibernate fetches within 1 query. This is a good approach to avoid n+1 select issues and improve the performance of your application. The following code snippet shows the annotation based definition of a named entity graph which tells Hibernate to fetch the Order entity together with all associated entities mapped by the items attribute.

@Entity
@NamedEntityGraph(name = "graph.Author.books", 
      attributeNodes = @NamedAttributeNode("books"))
public class Author {

  ...
  
  private List<Book> books = new ArrayList<Book>();
  
  ...
}

As you can see in the following code snippet, you can do the same with an XML configuration.

<entity-mappings>
  <entity class="org.thoughts.on.java.model.Author" name="Author">
    ...

    <named-entity-graph name="graph.Author.books">
            <named-attribute-node name="books" />
        </named-entity-graph>
  </entity>
  ...
</entity-mappings>

Annotations and XML in the same project

As you’ve seen, you can define your mappings and queries via annotations or XML. But what happens, when you use XML and annotations in the same project?

Just to be clear, I don’t recommend this approach. In general, you should only use one of them to keep your code readable and avoid any confusion.

But it’s supported by the JPA specification, and there are some situations in which it can be useful, like during the transition from an XML based to an annotation based mapping definition or to override the mapping of a shared component.

When you use both approaches within the same project, the mapping definitions defined by the annotations and in the orm.xml are used together. The XML configuration overrides the annotations if you define the same mapping via annotations and in the orm.xml file.

External XML configuration

By default, JPA and Hibernate check if an orm.xml file exists in the META-INF directory and load the mapping definitions from there. But if you want to use the XML configuration to override the entity mappings of a shared component, you might also want to store the XML configuration in an external file. You can do that by referencing the mapping configuration in the mapping-file
attribute in the persistence.xml file.

The following example loads the mapping definitions from the myMappings.xml file.

<persistence>
  <persistence-unit name="my-persistence-unit">
    <description>Thougths on Java</description>
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <mapping-file>file:\\\C:\dev\wrk\XmlMapping\XmlMappings\myMappings.xml</mapping-file>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />

      <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
      <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/test" />
      <property name="javax.persistence.jdbc.user" value="postgres" />
      <property name="javax.persistence.jdbc.password" value="postgres" />

      <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>        
    </properties>
  </persistence-unit>
</persistence>

Summary

As you’ve seen, JPA and Hibernate support annotation based and XML based mapping definitions.

By default, JPA and Hibernate load the XML based mapping definitions from the orm.xml file in the META-INF directory. You can change the name and path of that file with the mapping-file attribute in the persistence.xml file.

You can use both of them within the same project. If you do that, the XML mapping overrides the annotations.

6 Comments

  1. Thanks this article is informative.
    My question is – can we use any tool/ plugin to convert xml based mappings to Annotations based Entity ?
    If no any available tool to achieve this, please guide on high level steps.

    Thanks.

    1. Avatar photo Thorben Janssen says:

      Hi Saurabh,

      No, there is no tool at the moment. But you can refactor one mapping after the other and use the annotation and XML based mappings in parallel. That enables you to test and keep using your application while migration your mapping definitions.

      Regards,
      Thorben

      1. Thanks for your kind response.

  2. Avatar photo Anonymous says:

    ok nice explanation. but which one is faster? does annotation uses reflection or xml uses reflection?

    1. Avatar photo Thorben Janssen says:

      It doesn’t make any difference. The annotations and xml configuration are used at startup and have no performance impact at runtime.

  3. Avatar photo Binh Nguyen says:

    Thanks, nice explanation and examples

Comments are closed.