Graphique d’entité JPA

Graphique d'entité JPA

1. Vue d'ensemble

JPA 2.1 a introduit la fonction Entity Graph comme une méthode plus sophistiquée pour gérer le chargement des performances.

Il permet de définir un modèle en regroupant les champs de persistance associés que nous voulons récupérer et de choisir le type de graphique à l'exécution.

Dans ce didacticiel, nous expliquerons plus en détail comment créer et utiliser cette fonctionnalité.

2. Ce que le graphique d'entité tente de résoudre

Jusqu'au JPA 2.0, pour charger une association d'entité, nous utilisions généralement les stratégies de récupérationFetchType.LAZY etFetchType.EAGER as Cela indique au fournisseur JPA de récupérer ou non l'association associée. Unfortunately, this meta configuration is static et ne permet pas de basculer entre ces deux stratégies lors de l'exécution.

L'objectif principal du graphe d'entité JPA est alors d'améliorer les performances d'exécution lors du chargement des associations associées et des champs de base de l'entité.

En bref, le fournisseur JPA charge tout le graphique dans une requête de sélection et évite ensuite d'extraire l'association avec d'autres requêtes de type SELECT. Ceci est considéré comme une bonne approche pour améliorer les performances des applications.

3. Définir le modèle

Avant de commencer à explorer le graphique d'entité, nous devons définir les entités de modèle avec lesquelles nous travaillons. Supposons que nous souhaitons créer un site de blog sur lequel les utilisateurs peuvent commenter et partager des articles.

Donc, nous allons d'abord avoir une entitéUser:

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

    //...
}

L'utilisateur peut partager divers articles, nous avons donc également besoin d'une entitéPost:

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String subject;
    @OneToMany(mappedBy = "post")
    private List comments = new ArrayList<>();

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

    //...
}

L'utilisateur peut également commenter les messages partagés, donc, enfin, nous allons ajouter une entitéComment:

@Entity
public class Comment {

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

    private String reply;

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

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

    //...
}

Comme nous pouvons le voir, l'entitéPost est associée aux entitésComment etUser. L'entitéComment est associée aux entitésPost etUser.

Le but est alors de charger le graphique suivant de différentes manières:

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

La méthodeFetchType définit deux stratégies pour récupérer les données de la base de données:

  • FetchType.EAGER: Le fournisseur de persistance doit charger le champ ou la propriété annotée associé. Il s'agit du comportement par défaut pour les champs annotés@Basic, @ManyToOne et@OneToOne .

  • FetchType.LAZY: Le fournisseur de persistance doit charger les données lors de leur premier accès, mais peut être chargé rapidement. Il s'agit du comportement par défaut pour les champs annotés@OneToMany, @ManyToMany et@ElementCollection-.

Par exemple, lorsque nous chargeons une entitéPost, les entitésComment liées ne sont pas chargées commeFetchType par défaut puisque@OneToMany estLAZY. Nous pouvons remplacer ce comportement en changeant leFetchType enEAGER:

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

Par comparaison, lorsque nous chargeons une entitéComment, son entité parentPost est chargée comme mode par défaut pour@ManyToOne, which estEAGER. Nous pouvons également choisir de ne pas charger lePost entité en changeant cette annotation enLAZY:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;

Note that as the LAZY is not a requirement, the persistence provider can still load the Post entity eagerly if it wants. Donc, pour utiliser correctement cette stratégie, nous devrions revenir à la documentation officielle du fournisseur de persistance correspondant.

Maintenant, parce que nous avons utilisé des annotations pour décrire notre stratégie de récupération,our definition is static and there is no way to switch between the LAZY and EAGER at runtime.

C’est là que le graphique d’entité entre en jeu, comme nous le verrons dans la section suivante.

5. Définir un graphe d'entité

Pour définir un graphe d'entité, nous pouvons utiliser les annotations sur l'entité ou procéder par programme à l'aide de l'API JPA.

5.1. Définir un graphe d'entité avec des annotations

L'annotation @NamedEntityGraph permet de spécifier les attributs à inclure lorsque l'on souhaite charger l'entité et les associations associées.

Commençons par définir un graphique d'entité qui charge lesPost et ses entités associéesUser etComments:

@NamedEntityGraph(
  name = "post-entity-graph",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode("comments"),
  }
)
@Entity
public class Post {

    @OneToMany(mappedBy = "post")
    private List comments = new ArrayList<>();

    //...
}

Dans cet exemple, nous avons utilisé les@NamedAttributeNode pour définir les entités associées à charger lorsque l’entité racine est chargée.

Définissons maintenant un graphique d’entité plus compliqué où nous voulons également charger lesUsers liés auxComments.

À cette fin, nous utiliserons l’attribut@NamedAttributeNode subgraph. This allows referencing a named subgraph defined through the @NamedSubgraph annotation:

@NamedEntityGraph(
  name = "post-entity-graph-with-comment-users",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode(value = "comments", subgraph = "comments-subgraph"),
  },
  subgraphs = {
    @NamedSubgraph(
      name = "comments-subgraph",
      attributeNodes = {
        @NamedAttributeNode("user")
      }
    )
  }
)
@Entity
public class Post {

    @OneToMany(mappedBy = "post")
    private List comments = new ArrayList<>();
    //...
}

La définition de l'annotation@NamedSubgraph est similaire à celle des @NamedEntityGraph et permet de spécifier les attributs de l'association associée. Ce faisant, nous pouvons construire un graphique complet.

Dans l'exemple ci-dessus, avec le graphe 'post-entity-graph-with-comment-users' défini, nous pouvons charger lesPost,, lesUser, liés, lesComments et lesUser liés auComments.

Enfin, notez que nous pouvons également ajouter la définition du graphique d'entité en utilisant le descripteur de déploiementorm.xml:


  
    ...
    
            
    
  
  ...

5.2. Définition d'un graphe d'entité avec l'API JPA

Nous pouvons également définir le graphique d'entité via l'APIEntityManager en appelant la méthodecreateEntityGraph():

EntityGraph entityGraph = entityManager.createEntityGraph(Post.class);

Pour spécifier les attributs de l'entité racine, nous utilisons la méthodeaddAttributeNodes().

entityGraph.addAttributeNodes("subject");
entityGraph.addAttributeNodes("user");

De même, pour inclure les attributs de l'entité associée, nous utilisons lesaddSubgraph() pour construire un graphique d'entité intégré, puis nous lesaddAttributeNodes()  comme nous l'avons fait ci-dessus.

entityGraph.addSubgraph("comments")
  .addAttributeNodes("user");

Maintenant que nous avons vu comment créer le graphique d'entité, nous allons explorer comment l'utiliser dans la section suivante.

6. Utilisation du graphe d'entité

6.1. Types de graphiques d'entité

JPA définit deux propriétés ou astuces que le fournisseur de persistance peut choisir afin de charger ou d'extraire le graphe d'entité au moment de l'exécution:

  • javax.persistence.fetchgraph – Seuls les attributs spécifiés sont extraits de la base de données. As we are using Hibernate in this tutorial, we can note that in contrast to the JPA specs, attributes statically configured as EAGER are also loaded.

  • javax.persistence.loadgraph – En plus des attributs spécifiés, les attributs configurés statiquement en tant queEAGER sont également récupérés.

Dans les deux cas, la clé primaire et la version, le cas échéant, sont toujours chargées.

6.2. Chargement d'un graphique d'entité

Nous pouvons récupérer le graphe d'entité de différentes manières.

Let’s start by using the EntityManager.find() method. Comme nous l'avons déjà montré, le mode par défaut est basé sur les méta-stratégies statiquesFetchType.EAGER etFetchType.LAZY.

Appelons donc la méthodefind() et inspectons le journal:

Post post = entityManager.find(Post.class, 1L);

Voici le journal fourni par l'implémentation d'Hibernate:

select
    post0_.id as id1_1_0_,
    post0_.subject as subject2_1_0_,
    post0_.user_id as user_id3_1_0_
from
    Post post0_
where
    post0_.id=?

Comme nous pouvons le voir dans le journal, les entitésUser etComment ne sont pas chargées.

Nous pouvons remplacer ce comportement par défaut en invoquant la méthode surchargéefind() qui accepte les indices comme unMap. Nous pouvons alorsprovide the graph type which we want to load:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
Map properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
Post post = entityManager.find(Post.class, id, properties);

Si nous regardons à nouveau dans le journal, nous pouvons voir que ces entités sont maintenant chargées et que dans une requête de sélection:

select
    post0_.id as id1_1_0_,
    post0_.subject as subject2_1_0_,
    post0_.user_id as user_id3_1_0_,
    comments1_.post_id as post_id3_0_1_,
    comments1_.id as id1_0_1_,
    comments1_.id as id1_0_2_,
    comments1_.post_id as post_id3_0_2_,
    comments1_.reply as reply2_0_2_,
    comments1_.user_id as user_id4_0_2_,
    user2_.id as id1_2_3_,
    user2_.email as email2_2_3_,
    user2_.name as name3_2_3_
from
    Post post0_
left outer join
    Comment comments1_
        on post0_.id=comments1_.post_id
left outer join
    User user2_
        on post0_.user_id=user2_.id
where
    post0_.id=?

Voyons comment nous pouvons réaliser la même chose avec JPQL:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
Post post = entityManager.createQuery("select p from Post p where p.id = :id", Post.class)
  .setParameter("id", id)
  .setHint("javax.persistence.fetchgraph", entityGraph)
  .getSingleResult();

Et enfin, jetons un œil à un exemple d'APICriteria:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Post.class);
Root root = criteriaQuery.from(Post.class);
criteriaQuery.where(criteriaBuilder.equal(root.get("id"), id));
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
Post post = typedQuery.getSingleResult();

Dans chacun d'eux,the graph type is given as a hint. Alors que dans le premier exemple nous avons utilisé lesMap, dans les deux exemples suivants, nous avons utilisé la méthodesetHint().

7. Conclusion

Dans cet article, nous avons exploré l'utilisation du graphe d'entité JPA pour extraire dynamiquement unEntity et ses associations.

La décision est prise lors de l'exécution dans laquelle nous choisissons de charger ou non l'association associée.

Les performances sont évidemment un facteur clé à prendre en compte lors de la conception des entités JPA. La documentation JPA recommande d'utiliser la stratégieFetchType.LAZY chaque fois que cela est possible, et le graphique d'entité lorsque nous devons charger une association.

Comme d'habitude, tout le code est disponibleover on GitHub.