Spring Data JPA only supports EntityGraph through annotations.
Thus, for a repository method, you must select at most one EntityGraph before compilation.
This prevents you from choosing the best EntityGraph considering the runtime context 💔
Spring Data JPA EntityGraph allows you to choose EntityGraph at runtime! This choice is elegantly made by passing EntityGraph, as an argument, to any Spring Data JPA repository method 😍
-
Select the correct version from the compatibility matrix
-
In addition to Spring Data JPA, add Spring Data JPA EntityGraph dependency :
<dependency> <groupId>com.cosium.spring.data</groupId> <artifactId>spring-data-jpa-entity-graph</artifactId> <version>${spring-data-jpa-entity-graph.version}</version> </dependency>
-
Set the repository factory bean class to
EntityGraphJpaRepositoryFactoryBean:@SpringBootApplication @EnableJpaRepositories(repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class) public class App { //... }
- Moved from Java 8 to 17 as the source language
javax.persistencereplaced byjakarta.persistencecom.cosium.spring.data.jpa.entity.graph.domainclasses (deprecated in 2.7.x) have been removed in favor ofcom.cosium.spring.data.jpa.entity.graph.domain2Default EntityGraph by name patternfeature (deprecated in 2.7.x) has been removed.*.defaultnamed EntityGraph are not considered as default EntityGraph anymore. Please read default EntityGraph to use the newDefault EntityGraphfeature instead.
If you want to define a custom repository method accepting an EntityGraph, just do it™.
For example, given an entity having attribute named label of type String, you could declare and use a repository like this:
interface MyRepository extends Repository<MyEntity, Long> {
Optional<MyEntity> findByLabel(String label, EntityGraph entityGraph);
}myRepository.findByLabel("foo", NamedEntityGraph.loading("bar"));Spring Data JPA EntityGraph provides repository interfaces extending Spring Data JPA. For each Spring Data JPA pre-defined method, the provided interfaces overload the method with EntityGraph as an additional argument.
For example, Spring Data JPA CrudRepository defines method Optional<T> findById(ID id). This method is overloaded by Spring Data JPA EntityGraph EntityGraphCrudRepository as Optional<T> findById(ID id, EntityGraph entityGraph).
To be able to use these overloaded methods, you must extend one of Spring Data JPA EntityGraph provided repository interfaces.
The following matrix describes the mapping between Spring Data JPA and Spring Data JPA EntityGraph :
| Spring Data JPA | Spring Data JPA EntityGraph |
|---|---|
| JpaRepository | EntityGraphJpaRepository |
| JpaSpecificationExecutor | EntityGraphJpaSpecificationExecutor |
| QuerydslPredicateExecutor | EntityGraphQuerydslPredicateExecutor |
| CrudRepository | EntityGraphCrudRepository |
| PagingAndSortingRepository | EntityGraphPagingAndSortingRepository |
| QueryByExampleExecutor | EntityGraphQueryByExampleExecutor |
For example, if you wanted to use Optional<T> findById(ID id, EntityGraph entityGraph), you could declare and use your repository like this:
interface MyRepository extends EntityGraphCrudRepository<MyEntity, Long> {
}myRepository.findById(1L, NamedEntityGraph.loading("foo"));DynamicEntityGraph class allows you to create on-the-fly EntityGraph by defining their attribute paths. This is similar to Spring's ad-hoc attribute paths.
For example, let's consider the following entities :
@Entity
class Maker {
//...
@OneToOne(fetch = FetchType.LAZY)
private Address address;
//...
}@Entity
class Product {
@Id
private long id = 0;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Brand brand;
@ManyToOne(fetch = FetchType.LAZY)
private Maker maker;
//...
} You could declare the following repository :
public interface MyRepository extends Repository<Product, Long> {
List<Product> findByName(String name, EntityGraph entityGraph);
}Then perform the findByName using ad-hoc product(brand, maker(address)) EntityGraph :
myRepository.findById(1L, DynamicEntityGraph.loading().addPath("brand").addPath("maker", "address").build());NamedEntityGraph class allows you to reference an EntityGraph by its name. Such EntityGraph must have beforehand been registered through EntityManager#createEntityGraph(String graphName) or declared using @NamedEntityGraph annotation.
For example, let's consider the following entity :
@NamedEntityGraphs(value = {
@NamedEntityGraph(name = "productBrand", attributeNodes = {
@NamedAttributeNode("brand")
})
})
@Entity
public class Product {
@Id
private long id = 0;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Brand brand;
//...
} You could declare the following repository :
public interface MyRepository extends Repository<Product, Long> {
List<Product> findByName(String name, EntityGraph entityGraph);
}Then perform the findByName query using productBrand named EntityGraph like this :
myRepository.findByName("foo", NamedEntityGraph.loading("productBrand"));Composing entity graphs by hand can be tedious and error-prone. Wouldn't it be great to benefit from autocompletion and strong type checking while composing your entity graph?
Spring Data JPA EntityGraph Generator has you covered.
This annotation processor makes use of the JPA metamodel information (part of JPA specification) generated by the tool of your choice (e.g. hibernate-jpamodelgen) to generate EntityGraph composers allowing you to safely and easily compose EntityGraph at runtime.
You need to add:
- A JPA metamodel information generator.
hibernate-jpamodelgenis great and should be compatible with any JPA ORM. - The entity graph generator annotation processor dependency.
Your pom.xml should look like this:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate-jpamodelgen.version}</version>
</path>
<path>
<groupId>com.cosium.spring.data</groupId>
<artifactId>spring-data-jpa-entity-graph-generator</artifactId>
<version>${spring-data-jpa-entity-graph.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>After compiling your project, you should find XEntityGraph classes where X is the name of your Entity.
For example, let's consider the following entity :
@Entity
public class Product {
@Id
private long id = 0;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Brand brand;
//...
} You could declare the following repository :
public interface MyRepository extends Repository<Product, Long> {
List<Product> findByName(String name, EntityGraph entityGraph);
}spring-data-jpa-entity-graph-generator will detect Product and generate in return ProductEntityGraph class.
You could then perform the findByName query using product(brand, maker(address)) EntityGraph like this :
productRepository.findById(1L, ProductEntityGraph
.____()
.brand()
.____
.maker()
.address()
.____
.____());You can declare at most one default EntityGraph per repository by overriding EntityGraphRepository#defaultEntityGraph method.
Calling any repository query method - custom or pre-defined - without EntityGraph or with an EntityGraph#NOOP equivalent will lead to the default EntityGraph usage. Otherwise, the EntityGraph passed as query method argument will always have priority.
You could declare a repository as follows :
interface MyRepository extends EntityGraphCrudRepository<MyEntity, Long> {
@Override
default Optional<EntityGraph> defaultEntityGraph() {
return NamedEntityGraph.loading("foo").execute(Optional::of);
}
List<MyEntity> findByName(String name);
List<MyEntity> findByName(String name, EntityGraph entityGraph);
}The following snippets will lead to the default EntityGraph usage:
myRepository.findById(1L);myRepository.findById(1L, EntityGraph.NOOP);myRepository.findByName("bar");The following snippets will ignore the default EntityGraph and instead use the EntityGraph passed as argument:
myRepository.findById(1L, NamedEntityGraph.loading("alice"));myRepository.findByName("bar", NamedEntityGraph.loading("barry"));If you prefer fluent apis, you can use any instance of EntityGraph like this:
List<Product> products = ProductEntityGraph
.____()
.brand()
.____
.maker()
.address()
.____
.____()
.execute(entityGraph -> myRepository.findByLabel("foo", entityGraph));JPA 2.1 defines 2 semantics:
Spring Data JPA EntityGraph uses Load Graph Semantics as the default semantic. This means if you don't define a semantic, EntityGraph implementations will be built using Load Graph Semantics.
Each provided EntityGraph implementation provides an easy way to select the Graph Semantics.
You can play with https://github.com/Cosium/spring-data-jpa-entity-graph-sample to see the extension in action in a simple Spring Application.
| Spring Data JPA version | Spring Data JPA EntityGraph version |
|---|---|
| 3.5.x | |
| 3.2.1+ | |
| 3.2.0 | |
| 3.1.x | |
| 3.0.x | |
| 2.7.x | |
| 2.6.x | |
| 2.5.x | |
| 2.4.x | |
| 2.3.x | |
| 2.2.x | |
| 2.1.x | |
| 2.0.x | |
| 1.11.x | |
| 1.10.x |
For example, if you were using spring-data-jpa 2.2.x in your project, you would need to select any spring-data-jpa-entity-graph 2.2.x. Thus spring-data-jpa-entity-graph 2.2.8 would be eligible.
This talk was given at Paris JUG in January 2019.
The slides are in english.
The video is in French:
This project was created following spring-projects/spring-data-jpa#1120 discussion.