How to Initialize Entity Associations with Spring Data JPA
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 we’re talking about performance optimizations for Spring Data JPA, the handling of associations is always an important topic. Because Spring Data JPA is based on JPA and most often used with Hibernate, you can apply all the performance tuning concepts you can find here on the blog. The most important one is to use lazy fetching for all associations and combine it with query-specific fetching.
Ghasem wants to follow that advice and asked in a comment on a previous article for the best way to do that:
Defining your query using Spring Data’s @Query annotation is one option. It allows you to define a custom query, provides great flexibility, and is easy to use. But it’s not your only option. You can also add a @NamedEntityGraph reference to your query method or implement a custom repository method that uses JPA’s EntityGraph API. In this article, I will show you all 3 options and explain when you should use them.
@Query – Custom query with fetching behavior
Spring Data JPA’s repositories not only provide you with a set of ready-to-use methods to persist new entity objects or to fetch existing ones via their primary key. You can also provide your own JPQL or native SQL statement, and Spring Data provides the required code to execute it.
The only thing you need to do is add a method to your repository and annotate it with @Query. You can then provide your JPQL statement as the value of that annotation. Within that statement, you can use one or more JOIN FETCH clauses to specify the associations you want to initialize.
In the following code snippet, I use this approach to define a query that returns all Author entities with their books that have a given first and last name.
public interface AuthorRepository extends JpaRepository<Author, Long> { @Query("SELECT a FROM Author a LEFT JOIN FETCH a.books WHERE firstName = ?1 AND lastName = ?2") List<Author> findByFirstNameAndLastNameWithBooks(String firstName, String lastName); }
This is a great approach for all use case specific queries because it combines the query statement with the definition of the required fetching behavior. But sometimes, you want to execute the same query in multiple contexts that require different fetching behavior. You then either need to provide a separate repository method and query for each context, or you need to define the fetching behavior programmatically.
@EntityGraph – Add a graph reference to your repository method
JPA’s @NamedEntityGraph annotation enables you to create a query-independent graph definition that references the associations you want to initialize. I explained this in great detail in the first part of my guide to JPA’s entity graphs.
The following graph tells your persistence provider to fetch the books attribute of the entity returned by your query.
@Entity @NamedEntityGraph(name = "graph.Author.books", attributeNodes = @NamedAttributeNode(value = "books")) public class Author { ... }
In the next step, you need to combine this graph definition with a query that returns the entities for which you want to initialize the books attribute. Using plain JPA, this would require some boilerplate code. But thanks to Spring Data JPA, you can do the same by adding the @EntityGraph annotation to your repository method. Using this annotation, you can then reference the graph by its name and define if you want to use it as a fetch or a load graph.
public interface AuthorRepository extends JpaRepository<Author, Long> { @EntityGraph(value = "graph.Author.books", type = EntityGraphType.LOAD) List<Author> findByFirstNameAndLastName(String firstName, String lastName); }
Spring Data JPA then derives the query from the method name, instantiates an entity graph with the name graph.Author.books, combines the graph with the query, and executes it.
Composite repository – Programmatic fetching definition
Your 3rd option to fetch an association using Spring Data JPA is to use a composite repository. It’s by far the most flexible approach but it also requires the most work.
A composite repository enables you to add your own method implementation to a Spring Data JPA repository. This requires the definition of a fragment interface that defines the method you want to implement and a class that implements the fragment interface.
public interface CustomAuthorRepository { public List<Author> getAuthorsByFirstName(String firstName, boolean withBooks); }
Within the method implementation, you can use the EntityManager with all features supported by the JPA specification and your persistence provider, e.g., Hibernate. This, of course, would allow you to execute a JPQL query with a JOIN FETCH clause or to execute a query with a @NamedEntityGraph. But as I showed before, Spring Data JPA’s integrations are much easier to use.
A composite repository is only required if you want to use JPA’s EntityGraph API to define your graph at runtime. This can be useful if your graph definition depends on user input.
public class CustomAuthorRepositoryImpl implements CustomAuthorRepository { private EntityManager entityManager; public CustomAuthorRepositoryImpl(EntityManager entityManager) { this.entityManager = entityManager; } @Override public List<Author> getAuthorsByFirstName(String firstName, boolean withBooks) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Author> query = cb.createQuery(Author.class); Root<Author> root = query.from(Author.class); query.select(root) .where(cb.equal(root.get(Author_.firstName), firstName)); TypedQuery<Author> q = entityManager.createQuery(query); if (withBooks) { EntityGraph<Author> graph = this.em.createEntityGraph(Author.class); graph.addAttributeNodes("books"); q.setHint("javax.persistence.loadgraph", graph); } return q.getResultList(); } }
Conclusion
As you have seen, annotating your repository method with @Query and providing a JPQL statement with one or more JOIN FETCH clauses is by far the easiest approach to initialize an association. As long as there are no other requirements, this is my preferred approach.
If you’re using plain JPA, named entity graphs are useful to execute the same query with different fetching behaviors. But because Spring Data JPA makes it so easy to add custom queries to a repository, I usually prefer them over entity graphs.
The composite repository requires much more code than the 2 previously discussed options. That’s why I only recommend using it if your implementation benefits from the additional flexibility.