Carga de asociaciones en consultas con EntityGraph y anotaciones

Avanzado
Hibernate
Hibernate
Actualizado: 26/04/2026

EntityGraph programático

Introducción

Además de definir EntityGraphs de manera programática, Hibernate permite definir EntityGraphs de manera estática utilizando anotaciones directamente en las clases de entidad. Esto proporciona una forma declarativa de especificar las cargas de entidades, que puede ser más limpia y menos propensa a errores en ciertos contextos.

Anotaciones

  • @NamedEntityGraph: Se utiliza para definir un EntityGraph a nivel de clase de entidad en JPA. Permite especificar un conjunto de atributos y relaciones que deben cargarse de manera Eager o Lazy cuando se utiliza el EntityGraph en una consulta. Puede contener múltiples @NamedAttributeNode y @NamedSubgraph para detallar la estructura del grafo.
  • @NamedAttributeNode: Define un atributo específico de una entidad que debe ser incluido en el EntityGraph. Puede ser un atributo simple o una relación a otra entidad. En caso de ser una relación, puede asociarse a un @NamedSubgraph para especificar más detalles sobre cómo cargar la entidad relacionada.
  • @NamedSubgraph: Se utiliza dentro de un @NamedAttributeNode cuando el atributo es una relación a otra entidad. Permite definir un subconjunto de atributos de la entidad relacionada que también deben cargarse de manera Eager o Lazy. Proporciona una manera de gestionar cargas más complejas y detalladas de entidades relacionadas dentro de un EntityGraph.

Ejemplo de anotación de una entidad mediante EntityGraph:

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.NamedAttributeNode;
import jakarta.persistence.NamedSubgraph;

@Entity
@NamedEntityGraph(
    name = "empleadoDetalle",
    attributeNodes = {
        @NamedAttributeNode(value = "nombre"),
        @NamedAttributeNode(value = "apellido"),
        @NamedAttributeNode(value = "departamento", subgraph = "departamentoDetalle")
    },
    subgraphs = {
        @NamedSubgraph(
            name = "departamentoDetalle",
            attributeNodes = {
                @NamedAttributeNode("nombre")
            }
        )
    }
)
public class Empleado {
    // campos de la entidad, getters y setters
}

Ejemplo de consulta:

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.TypedQuery;

EntityManager em = ...; // Obtener el EntityManager
EntityGraph<?> graph = em.getEntityGraph("empleadoDetalle");

TypedQuery<Empleado> query = em.createQuery("SELECT e FROM Empleado e WHERE e.id = :idEmpleado", Empleado.class);
query.setParameter("idEmpleado", 1);
query.setHint("jakarta.persistence.loadgraph", graph);
Empleado empleado = query.getSingleResult();

En este ejemplo, el EntityGraph llamado "empleadoDetalle" se utiliza para cargar un empleado con su nombre, apellido, y detalles del departamento asociado, de acuerdo con lo especificado en las anotaciones de la clase Empleado.

Otras anotaciones de EntityGraph usadas en JPA son:

  • @EntityGraph: Esta anotación se puede usar directamente en métodos de repositorios para definir dinámicamente un EntityGraph que se aplicará durante la ejecución de una consulta particular. Es común en Spring Data JPA.
  • @NamedEntityGraphs: Es una anotación contenedora que permite especificar múltiples @NamedEntityGraph en una sola clase de entidad. Esto es útil para definir diferentes configuraciones de carga de entidades en la misma clase según diferentes contextos o necesidades.

Diferencia entre fetchgraph y loadgraph

Cuando se aplica un EntityGraph como hint en una consulta, JPA admite dos modos que se comportan de forma distinta:

  • jakarta.persistence.fetchgraph: solo se cargan con EAGER los atributos listados en el grafo. Cualquier otro atributo no incluido se trata como LAZY, independientemente de su anotación original. Es la opción mas estricta y la recomendada para escenarios de lectura optimizada.
  • jakarta.persistence.loadgraph: se cargan con EAGER los atributos del grafo y, además, los que ya estaban marcados como EAGER en la entidad. El resto sigue siendo LAZY. Es útil cuando se quiere extender una configuración existente sin modificarla.
TypedQuery<Empleado> q = em.createQuery(
    "SELECT e FROM Empleado e WHERE e.activo = true", Empleado.class);
q.setHint("jakarta.persistence.fetchgraph", em.getEntityGraph("empleadoDetalle"));
List<Empleado> activos = q.getResultList();

Spring Data JPA

En proyectos Spring Boot, la anotación @EntityGraph se aplica directamente sobre los métodos del repositorio para activar un grafo concreto al ejecutar la consulta:

public interface EmpleadoRepository extends JpaRepository<Empleado, Long> {

    @EntityGraph(value = "empleadoDetalle", type = EntityGraph.EntityGraphType.FETCH)
    List<Empleado> findByActivoTrue();

    @EntityGraph(attributePaths = {"departamento", "departamento.empresa"})
    Optional<Empleado> findByEmail(String email);
}

attributePaths permite construir grafos ad-hoc sin declarar @NamedEntityGraph, lo que resulta comodo para casos puntuales del repositorio. Cuando el mismo grafo se reutiliza en varias consultas conviene declararlo una sola vez en la entidad y referenciarlo por nombre.

Una buena practica es definir un grafo por cada caso de uso de lectura (listado, detalle, exportación) y referenciarlo desde el repositorio. Así se evita el problema N+1 sin contaminar las entidades con EAGER permanentes.

API programática de EntityGraph

flowchart LR
    A[em.createEntityGraph Pedido] --> B[entityGraph]
    B --> C[addAttributeNodes cliente]
    B --> D[addSubgraph líneas]
    D --> E[addAttributeNodes producto]
    F[query.setHint] --> G{fetchgraph vs loadgraph}
    G -->|jakarta.persistence.fetchgraph| H[solo cargar nodos del grafo]
    G -->|jakarta.persistence.loadgraph| I["grafo + EAGER por defecto"]
    B --> F

Diferencia clave entre fetchgraph y loadgraph: el primero ignora los EAGER no especificados; el segundo los respeta. En producción casi siempre se usa fetchgraph por control explícito.

API programática frente a anotaciones

Con anotaciones @NamedEntityGraph, los grafos se declaran junto a la entidad y se reutilizan:

@Entity
@NamedEntityGraph(
    name = "Pedido.conClienteYLineas",
    attributeNodes = {
        @NamedAttributeNode("cliente"),
        @NamedAttributeNode(value = "lineas", subgraph = "lineas-producto")
    },
    subgraphs = @NamedSubgraph(
        name = "lineas-producto",
        attributeNodes = @NamedAttributeNode("producto")
    )
)
public class Pedido { ... }

Y se aplica:

EntityGraph<?> graph = em.getEntityGraph("Pedido.conClienteYLineas");
List<Pedido> pedidos = em.createQuery("SELECT p FROM Pedido p", Pedido.class)
    .setHint("jakarta.persistence.fetchgraph", graph)
    .getResultList();

Con la API programática, los grafos se construyen dinámicamente:

EntityGraph<Pedido> graph = em.createEntityGraph(Pedido.class);
graph.addAttributeNodes("cliente");
Subgraph<LineaPedido> lineasGraph = graph.addSubgraph("lineas");
lineasGraph.addAttributeNodes("producto");

List<Pedido> pedidos = em.createQuery("SELECT p FROM Pedido p", Pedido.class)
    .setHint("jakarta.persistence.fetchgraph", graph)
    .getResultList();

fetchgraph vs loadgraph

Diferencia sutil pero importante:

| Hint | Comportamiento | |------|----------------| | jakarta.persistence.fetchgraph | Solo carga lo que está en el grafo. Resto LAZY (incluso si era EAGER por defecto). | | jakarta.persistence.loadgraph | Carga lo del grafo + todo lo que era EAGER por defecto. |

En producción casi siempre se usa fetchgraph para tener control explícito y evitar sorpresas.

Spring Data JPA y @EntityGraph

Con Spring Data JPA, los grafos se aplican declarativamente en repositorios:

public interface PedidoRepository extends JpaRepository<Pedido, Long> {

    @EntityGraph(attributePaths = {"cliente", "lineas.producto"})
    List<Pedido> findByEstado(EstadoPedido estado);

    @EntityGraph(value = "Pedido.conClienteYLineas")
    Optional<Pedido> findById(Long id);

    @EntityGraph(attributePaths = "cliente")
    Page<Pedido> findByClienteNombreContaining(String nombre, Pageable pageable);
}

attributePaths permite definir el grafo inline. value apunta a un @NamedEntityGraph declarado.

Subgraphs anidados con notación punteada

Cuando se necesita navegar varios niveles, la notación con punto es la forma compacta:

@EntityGraph(attributePaths = {
    "cliente",
    "lineas",
    "lineas.producto",
    "lineas.producto.categoria"
})
List<Pedido> findAllConGrafoCompleto();

Equivale a construir subgraphs anidados con la API programática.

Buenas prácticas

  • Un grafo por caso de uso: no intentar un grafo único "que sirva para todo".
  • Nombrar bien los @NamedEntityGraph: Pedido.conLineas, Pedido.parDashboard, etc.
  • Validar con SQL log: confirmar que el grafo genera el JOIN esperado.
  • fetchgraph por defecto: evita el EAGER hidden.
  • Evitar grafos con muchas colecciones: MultipleBagFetchException. Si necesitas varias listas, separar en queries.
  • Combinar con paginación cuidadosamente: con colecciones, la paginación se hace en memoria. Mejor cargar IDs primero.

Ejemplo de medición

Antes:

// Sin grafo
List<Pedido> pedidos = repo.findAll();
for (Pedido p : pedidos) {
    p.getCliente().getNombre();
    p.getLineas().forEach(l -> l.getProducto().getNombre());
}
// 1 + N + N*M queries

Después:

@EntityGraph(attributePaths = {"cliente", "lineas.producto"})
List<Pedido> findAll();
// 1 query con LEFT JOINs

En un dataset de 100 pedidos con 5 líneas cada uno: de 1 + 100 + 500 = 601 queries a 1 query. Tiempo de respuesta cae de 3 segundos a 80 ms.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Hibernate

Documentación oficial de Hibernate
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Hibernate es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Hibernate

Explora más contenido relacionado con Hibernate y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Definir Entity Graphs declarativos con @NamedEntityGraph, @NamedAttributeNode y @NamedSubgraph para reutilizar perfiles de carga sin construirlos en cada consulta.