JPA Entity Graph and the nuances of its use

Entity Graph is one of the powerful JPA tools that helps developers flexibly manage the loading of related entities. Entity Graph allows you to dynamically configure data loading at runtime, which makes it especially useful in projects with complex data structures.

Team Spring AIO I prepared an article in which I looked at how to use Entity Graph.


In JPA 2.1, a feature was added Entity Graphwhich improves the performance of loading related entities. Previously, in JPA 2.0, two approaches were used to manage data loading strategies: FetchType.LAZY And FetchType.EAGERHowever, these strategies are static, which does not allow flexible switching between them during program execution.

Of course it exists join fetchwhich allows you to turn lazy loading into greedy loading with the JOIN strategy. But the opposite method — to turn greedy into lazy — is not provided by JPQL. And here comes to the rescue @EntityGraph! Let's figure out how to work with it in more detail…

Before we dive into Entity Graph, let's define the domain we're going to work with. Let's say we want to create a blog site where users can comment on posts and share them.

So, first let's declare an entity User:

@Entity
public class User {
  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;
}

The user can share different posts, so we also need an entity Post:

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String subject;

    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
    private List<Comment> comments = new ArrayList<>();

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id")
    private User user;
}

The user can also comment on published messages, so let's add an entity Comment:

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String reply;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn
    private Post post;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn
    private User user;
}

Essence Post has a connection with entities Comment And User. Essence Comment has a connection with entities Post And User.

Our task is to load the graph:

Post  ->  user:User
      ->  comments:List<Comment>
            comments[0]:Comment -> user:User
            comments[1]:Comment -> user:User

Before Entity Graph, developers used FetchType strategies to control how related data was loaded:

FetchType.EAGER: The entity is loaded together with its relationships (but not always in one query). This approach is used by default for annotations @ManyToOne And @OneToOne.

FetchType.LAZY: Related data is loaded only when it is requested. This is the default behavior for relationships. @OneToMany, @ManyToMany And @ElementCollection.

For example, when using LAZY for an entity Postcomments to the post (entity Comment) will not be loaded by default:

@OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();

At the same time for communication ManyToOne default behavior – EAGERwhich results in the automatic loading of related data:

@ManyToOne
@JoinColumn(name = "user_id")
private User user;

However, to make loading more flexible and manageable at the execution level, Entity Graph is used.

Let's look at an example using @EntityGraph and Spring Data:

@EntityGraph(attributePaths = {"comments"})
List<Post> findEntityGraphTypeFetchBySubject(String subject);

The graph will include loading all the basic fields of the user who created the post (user), as well as comments and their authors.

Hibernate: 
    select
        p1_0.id,
        c1_0.post_id,
        c1_0.id,
        c1_0.reply,
        c1_0.user_id,
        p1_0.subject,
        p1_0.user_id 
    from
        post p1_0 
    left join
        comment c1_0 
            on p1_0.id=c1_0.post_id 
    where
        p1_0.subject=?

It is worth noting that EntityGraph itself also has 2 types of loading: EntityGraph.EntityGraphType.FETCH And EntityGraph.EntityGraphType.LOAD. When Fetch mode is selected (used by default), associative attributes that are explicitly declared for loading, such as comments in our case, will be fetched greedily (FetchType.EAGER), while other attributes will be loaded lazily (FetchType.LAZY).

In case we use the mode EntityGraph.EntityGraphType.LOAD, selected attributes will be loaded eagerly, and the rest of the attributes will be loaded according to the FetchType specified in the model.

@EntityGraph(attributePaths = {"comments"}, type = EntityGraph.EntityGraphType.LOAD)
List<Post> findEntityGraphTypeLoadBySubject(String subject);
Hibernate: 
    select
        p1_0.id,
        c1_0.post_id,
        c1_0.id,
        c1_0.reply,
        c1_0.user_id,
        p1_0.subject,
        p1_0.user_id 
    from
        post p1_0 
    left join
        comment c1_0 
            on p1_0.id=c1_0.post_id 
    where
        p1_0.subject=?
Hibernate: 
    select
        u1_0.id,
        u1_0.email,
        u1_0.name 
    from
        user_ u1_0 
    where
        u1_0.id=?
Hibernate: 
    select
        u1_0.id,
        u1_0.email,
        u1_0.name 
    from
        user_ u1_0 
    where
        u1_0.id=?

//И так далее, N+1 запрос

As you can see, all user information is loaded as specified in the model – that is, greedily, even though we did not specify the user field for @EntityGraph.

Separately, we note that the basic attributes will always be loaded, regardless of the selected loading mode, whether it be FETCH or LOAD. Therefore, there is no point in specifying them as values ​​for attributePaths.

Join the Russian-speaking Spring Boot developer community in telegram – Spring AIOto stay up to date with the latest news from the world of Spring Boot development and everything related to it.

We are waiting for everyone, join us

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *