|

Create type-safe queries with the JPA static metamodel


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.


When you write a criteria query or create a dynamic entity graph, you need to reference the entity classes and their attributes. The quickest and easiest way is to provide the required names as Strings. But this has several drawbacks, e.g. you have to remember or look-up all the names of the entity attributes when you write the query. But it will also cause even greater issues at later phases of the project if you have to refactor your entities and change the names of some attributes. In that case, you have to use the search function of your IDE and try to find all Strings that reference the changed attributes. This is a tedious and error-prone activity which will easily take up the most time of the refactoring.

Therefore I prefer to use the static metamodel to write criteria queries and dynamic entity graphs. This is a small feature defined by the JPA specification which provides a type-safe way to reference the entities and their properties.

Example entity

As you can see in the code snippets below, I prepared a simple entity for this example. It represents an Author with an id, a first and a last name and a List of books she/he has written. I skip the Book entity here because we don’t need it for the following examples.

@Entity
public class Author implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "id", updatable = false, nullable = false)
	private Long id;
	
	@Version
	@Column(name = "version")
	private int version;

	@Column
	private String firstName;

	@Column
	private String lastName;
	
	@ManyToMany(mappedBy="authors")
	private Set<Book> books = new HashSet<Book>();
	
	...
}

Static metamodel class

The class of the static metamodel looks similar to the entity. Based on the JPA specification, there is a corresponding metamodel class for every managed class in the persistence unit. You can find it in the same package and it has the same name as the corresponding managed class with an added ‘_’ at the end. So the metamodel class in this example is located in the package org.thoughts.on.java.model and has the name Author_.

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Author.class)
public abstract class Author_ {

	public static volatile SingularAttribute<Author, String> firstName;
	public static volatile SingularAttribute<Author, String> lastName;
	public static volatile SetAttribute<Author, Book> books;
	public static volatile SingularAttribute<Author, Long> id;
	public static volatile SingularAttribute<Author, Integer> version;

}

As you see in the source code, the metamodel class Author_ provides an attribute for each attribute of the Author entity. Each metamodel attribute provides information about its type and the Entity to which it belongs.

Using metamodel classes

You can use the metamodel classes in the same way as you use the String reference to the entities and attributes. The APIs for criteria queries and dynamic entity graphs provide overloaded methods that accept Strings and implementations of the Attribute interface.

I use the metamodel class to create a criteria query to search for all Authors whose first name starts with “J”. As you can see, I use the same methods as I would do with the String references to the entity attributes.

CriteriaBuilder cb = this.em.getCriteriaBuilder();

// create the query
CriteriaQuery<Author> q = cb.createQuery(Author.class);

// set the root class
Root<Author> a = q.from(Author.class);

// use metadata class to define the where clause
q.where(cb.like(a.get(Author_.firstName), "J%"));

// perform query
this.em.createQuery(q).getResultList();

As I described earlier, the metamodel classes can also be used to create dynamic entity graphs. You can see an example for this in the following code snippet.

// create the entity graph
EntityGraph graph = this.em.createEntityGraph(Author.class);
// use metadata class to define the subgraph
Subgraph<Book> bookSubGraph = graph.addSubgraph(Author_.books);

// perform query
List<Author> authors = this.em
				.createQuery("SELECT DISTINCT a FROM Author a", Author.class)
				.setHint("javax.persistence.fetchgraph", graph).getResultList();

Generating metamodel classes

OK, wait a second before you go ahead and start to create metamodel classes for your entities. There is no need for that. The JPA specification suggests to use an annotation processor to generate the metamodel classes and that is what the different implementations do. Unfortunately each and every implementation provides its own way for it.

I describe the required maven build configuration for Hibernate below. If you use a different JPA implementation (e.g. EclipseLink, OpenJPA) or build tool, please check the according documentation.

Adding the Hibernate Static Metamodel Generator to your build process is extremely simple. You only need to add it to your build classpath. The following code snippet shows the required dependency declaration for a maven build.

...

<dependencies>
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-jpamodelgen</artifactId>
	</dependency>
</dependencies>

...

If you use maven, the generated metamodel classes are stored in the target/generated-classes folder. So you need to add this folder to the classpath definition in your IDE.

Conclusion

The static metamodel provides a type-safe and easy way to create criteria queries and dynamic entity graphs. This speeds up the initial implementation and makes future refactorings much easier than referencing the attributes via Strings.

The different persistence provider implementations provide annotation processors to generate the metamodel classes at build time. This makes sure, that changes on the entities are reflected on the metamodel and avoids bugs at runtime.

6 Comments

  1. Hello Thorben,

    during Testing all the members of the generated classes are null like for instance Author_.firstName.

    Is there anything I can do?

    Kind regards
    Stephan.

    PS: Thanks for this and all your other posts. I really learned a lot from you!

    1. Avatar photo Thorben Janssen says:

      Hi Stephan,

      I’m not sure if I understand your question. May you please share some code and explain your question in more details.

      Thanks!
      Thorben

  2. Hi Thorben,

    Thanks for the post! It was really helpful. However, I ran into an issue when it comes to GregorianCalendar objects in entities. They do not get picked up by the generator, meaning that the generated metamodels don’t have the SingularAttribute fields. I’ve done a lot of research and there seems to have no resource anywhere that explains this issue.

    Please advise!

    Best,
    Vincent

    1. Avatar photo Thorben Janssen says:

      Hi Vincent,

      That looks like a bug.
      You can use the GregorianCalendar as an entity attribute type. Officially, JPA and Hibernate support java.util.Calendar but don’t mention the GregorianCalendar (which is a subclass of java.util.Calendar). Hibernate should be able to and does map these attributes anyways.

      So, I would expect that attributes of type GregorianCalendar should be supported by the metamodel generator. I’m sorry, but I don’t know why it doesn’t work and my only suggestion would be to switch to the Date and Time API (Hibernate 5: How to persist LocalDateTime & Co with Hibernate).

      Regards,
      Thorben

  3. Hi, thanks for this post, my team was having some difficulty in having the Meta model classes into some other package than the actual model class, while trying to investigate, I see that it is not being supported by Hibernate (and actually JPA such as doesn’t specify that support).

    is this true? could you please confirm?

    Thanks
    Hari

    1. Avatar photo Thorben Janssen says:

      Hi Hari,

      as far as I know, there is no way to generate the meta model classes into a different package. By default they are generated into a different folder then your sources. So you have some kind of separation between your own classes and the generated ones.

      Regards,
      Thorben

Comments are closed.