Imagine having a tool that can automatically detect JPA and Hibernate performance issues.
Hypersistence Optimizer is that tool!
Introduction
In this article, I’m going to show you how to generate the JPA Metamodel using Hibernate, and how you can use it to write typesafe Criteria API queries.
Domain Model
Let’s assume our application uses the following Post, PostComment, PostDetails, and Tag entities, which form a one-to-many, one-to-one, and many-to-many table relationships:
How to generate the JPA Criteria Metamodel
The hibernate-jpamodelgen tool provided by Hibernate ORM can be used to scan the project entities and generate the JPA Criteria Metamodel. All you need to do is add the following annotationProcessorPath to the maven-compiler-plugin in the Maven pom.xml configuration file:
Now, when the project is compiled, you can see that in the target folder, the following Java classes are generated:
> tree target/generated-sources/
target/generated-sources/
└── annotations
└── com
└── vladmihalcea
└── book
└── hpjp
└── hibernate
├── forum
│ ├── PostComment_.java
│ ├── PostDetails_.java
│ ├── Post_.java
│ └── Tag_.java
Tag entity Metamodel
If the Tag entity is mapped as follows:
@Entity
@Table(name = "tag")
public class Tag {
@Id
private Long id;
private String name;
//Getters and setters omitted for brevity
}
The Tag_ Metamodel class is generated like this:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Tag.class)
public abstract class Tag_ {
public static volatile SingularAttribute<Tag, String> name;
public static volatile SingularAttribute<Tag, Long> id;
public static final String NAME = "name";
public static final String ID = "id";
}
The SingularAttribute is used for the basic id and nameTag JPA entity attributes.
Post entity Metamodel
The Post entity is mapped like this:
@Entity
@Table(name = "post")
public class Post {
@Id
private Long id;
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
@OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
@LazyToOne(LazyToOneOption.NO_PROXY)
private PostDetails details;
@ManyToMany
@JoinTable(
name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<>();
//Getters and setters omitted for brevity
}
The Post entity has two basic attributes, id and title, a one-to-many comments collection, a one-to-one details association, and a many-to-many tags collection.
The Post_ Metamodel class is generated as follows:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Post.class)
public abstract class Post_ {
public static volatile ListAttribute<Post, PostComment> comments;
public static volatile SingularAttribute<Post, PostDetails> details;
public static volatile SingularAttribute<Post, Long> id;
public static volatile SingularAttribute<Post, String> title;
public static volatile ListAttribute<Post, Tag> tags;
public static final String COMMENTS = "comments";
public static final String DETAILS = "details";
public static final String ID = "id";
public static final String TITLE = "title";
public static final String TAGS = "tags";
}
The basic id and title attributes, as well as the one-to-one details association, are represented by a SingularAttribute while the comments and tags collections are represented by the JPA ListAttribute.
PostDetails entity Metamodel
The PostDetails entity is mapped like this:
@Entity
@Table(name = "post_details")
public class PostDetails {
@Id
@GeneratedValue
private Long id;
@Column(name = "created_on")
private Date createdOn;
@Column(name = "created_by")
private String createdBy;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "id")
private Post post;
//Getters and setters omitted for brevity
}
All entity attributes are going to be represented by the JPA SingularAttribute in the associated PostDetails_ Metamodel class:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(PostDetails.class)
public abstract class PostDetails_ {
public static volatile SingularAttribute<PostDetails, Post> post;
public static volatile SingularAttribute<PostDetails, String> createdBy;
public static volatile SingularAttribute<PostDetails, Long> id;
public static volatile SingularAttribute<PostDetails, Date> createdOn;
public static final String POST = "post";
public static final String CREATED_BY = "createdBy";
public static final String ID = "id";
public static final String CREATED_ON = "createdOn";
}
PostComment entity Metamodel
The PostComment is mapped as follows:
@Entity
@Table(name = "post_comment")
public class PostComment {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
private String review;
//Getters and setters omitted for brevity
}
And, all entity attributes are represented by the JPA SingularAttribute in the associated PostComments_ Metamodel class:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(PostComment.class)
public abstract class PostComment_ {
public static volatile SingularAttribute<PostComment, Post> post;
public static volatile SingularAttribute<PostComment, String> review;
public static volatile SingularAttribute<PostComment, Long> id;
public static final String POST = "post";
public static final String REVIEW = "review";
public static final String ID = "id";
}
Using the JPA Criteria Metamodel
Without the JPA Metamodel, a Criteria API query that needs to fetch the PostComment entities filtered by their associated Post title would look like this:
Writing JPA Criteria API queries is much easier if you are using a code completion tool like Codota. Check out this article for more details about the Codota IDE plugin.
Or, let’s say we want to fetch a DTO projection while filtering the Posttitle and the PostDetailscreatedOn attributes.
We can use the Metamodel when creating the join attributes, as well as when building the DTO projection column aliases or when referencing the entity attributes we need to filter:
The JPA Metamodel is very useful as it allows us to build typesafe Criteria API queries. Generating the Metamodel is straightforward, as Hibernate offers the jpamodelgen tool for this purpose.
If we rename an entity attribute we no longer have to worry that we missed updating a query because the project is not even going to compile if the Metamodel changes and queries reference an old JPA entity representation.