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.persistence
replaced byjakarta.persistence
com.cosium.spring.data.jpa.entity.graph.domain
classes (deprecated in 2.7.x) have been removed in favor ofcom.cosium.spring.data.jpa.entity.graph.domain2
Default EntityGraph by name pattern
feature (deprecated in 2.7.x) has been removed.*.default
named EntityGraph are not considered as default EntityGraph anymore. Please read default EntityGraph to use the newDefault EntityGraph
feature 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-jpamodelgen
is 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.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.