La búsqueda ansiosa de JPA no se une


112

¿Qué controla exactamente la estrategia de recuperación de JPA? No puedo detectar ninguna diferencia entre impaciente y vago. En ambos casos, JPA / Hibernate no une automáticamente relaciones de varios a uno.

Ejemplo: la persona tiene una sola dirección. Una dirección puede pertenecer a muchas personas. Las clases de entidad anotadas de JPA se ven así:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

Si uso la consulta JPA:

select p from Person p where ...

JPA / Hibernate genera una consulta SQL para seleccionar de la tabla Person, y luego una consulta de dirección distinta para cada persona:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

Esto es muy malo para conjuntos de resultados grandes. Si hay 1000 personas, genera 1001 consultas (1 de Persona y 1000 distintas de Dirección). Lo sé porque estoy mirando el registro de consultas de MySQL. Tenía entendido que establecer el tipo de búsqueda de la dirección en ansioso hará que JPA / Hibernate consulte automáticamente con una combinación. Sin embargo, independientemente del tipo de búsqueda, todavía genera consultas distintas para las relaciones.

Solo cuando le digo explícitamente que se una, realmente se une:

select p, a from Person p left join p.address a where ...

¿Me estoy perdiendo de algo? Ahora tengo que codificar manualmente cada consulta para que se una a las relaciones de muchos a uno. Estoy usando la implementación JPA de Hibernate con MySQL.

Editar: Parece (consulte las preguntas frecuentes de Hibernate aquí y aquí ) que FetchTypeno afecta las consultas de JPA. Entonces, en mi caso, le he dicho explícitamente que se una.


5
Los enlaces a las entradas de las preguntas frecuentes están rotos, aquí está funcionando uno
n0weak

Respuestas:


99

JPA no proporciona ninguna especificación sobre las anotaciones de mapeo para seleccionar la estrategia de búsqueda. En general, las entidades relacionadas se pueden recuperar de cualquiera de las formas que se indican a continuación.

  • SELECCIONAR => una consulta para entidades raíz + una consulta para entidad / colección mapeada relacionada de cada entidad raíz = (n + 1) consultas
  • SUBSELECT => una consulta para entidades raíz + segunda consulta para entidad / colección mapeada relacionada de todas las entidades raíz recuperadas en la primera consulta = 2 consultas
  • JOIN => una consulta para buscar ambas entidades raíz y toda su entidad / colección mapeada = 1 consulta

Entonces SELECTy JOINson dos extremos y SUBSELECTse encuentra en el medio. Uno puede elegir la estrategia adecuada en función de su modelo de dominio.

De forma predeterminada, SELECTlo utilizan tanto JPA / EclipseLink como Hibernate. Esto se puede anular usando:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

en hibernación. También permite configurar el SELECTmodo explícitamente usando el @Fetch(FetchMode.SELECT)cual se puede ajustar usando el tamaño del lote, por ejemplo @BatchSize(size=10).

Las anotaciones correspondientes en EclipseLink son:

@JoinFetch
@BatchFetch

5
¿Por qué existen esos ajustes? Creo que JOIN debe usarse casi siempre. Ahora tengo que marcar todas las asignaciones con anotaciones específicas de hibernación.
vbezhenar

4
Interesante pero lamentablemente @Fetch (FetchMode.JOIN) no funciona en absoluto para mí (Hibernate 4.2.15), en JPQL como en Criteria.
Aphax

3
La anotación Hibernate tampoco parece funcionar en absoluto para mí, usando Spring JPA
TheBakker

2
@Aphax Esto podría deberse a que Hibernate usa diferentes estrategias predeterminadas para JPAQL / Criteria vs em.find (). Consulte vladmihalcea.com/2013/10/17/… y la documentación de referencia.
Joshua Davis

1
@vbezhenar (y otros leyendo su comentario algún tiempo después): La consulta JOIN genera un producto cartesiano en la base de datos, por lo que debe asegurarse de que desea que se calcule ese producto cartesiano. Tenga en cuenta que si usa la combinación de búsqueda, incluso si pone LAZY, se cargará con impaciencia.
Walfrat

45

"mxc" es correcto. fetchTypesolo especifica cuándo se debe resolver la relación.

Para optimizar la carga ansiosa mediante el uso de una combinación externa, debe agregar

@Fetch(FetchMode.JOIN)

a tu campo. Esta es una anotación específica de hibernación.


6
Esto no me funciona con Hibernate 4.2.15, en JPQL o Criteria.
Aphax

6
@Aphax Creo que eso se debe a que JPAQL y Criteria no obedecen a la especificación Fetch. La anotación Fetch solo funciona para em.find (), AFAIK. Ver vladmihalcea.com/2013/10/17/… También, ver los docos de hibernación. Estoy bastante seguro de que esto está cubierto en alguna parte.
Joshua Davis

@JoshuaDavis Lo que quiero decir es que la anotación \ @Fetch no aplica ningún tipo de optimización JOIN en las consultas, ya sea JPQL o em.find (), acabo de probar de nuevo Hibernate 5.2. + Y sigue siendo el mismo
Aphax

38

El atributo fetchType controla si el campo anotado se recupera inmediatamente cuando se recupera la entidad principal. No necesariamente dicta cómo se construye la declaración de recuperación, la implementación real de sql depende del proveedor que esté utilizando toplink / hibernate, etc.

Si establece fetchType=EAGEREsto significa que el campo anotado se completa con sus valores al mismo tiempo que los demás campos de la entidad. Entonces, si abre un entitymanager, recupera sus objetos de persona y luego cierra el entitymanager, posteriormente, hacer una person.address no dará como resultado una excepción de carga diferida.

Si establece, fetchType=LAZYel campo solo se rellena cuando se accede a él. Si ha cerrado el entitymanager para entonces, se lanzará una excepción de carga diferida si hace una person.address. Para cargar el campo, debe volver a poner la entidad en un contexto de entitymangers con em.merge (), luego hacer el acceso al campo y luego cerrar el entitymanager.

Es posible que desee una carga diferida al construir una clase de cliente con una colección para pedidos de clientes. Si recuperó todos los pedidos de un cliente cuando deseaba obtener una lista de clientes, esta operación de base de datos puede ser costosa cuando solo busca el nombre del cliente y los detalles de contacto. Es mejor dejar el acceso a la base de datos para más tarde.

Para la segunda parte de la pregunta: ¿cómo hacer que hibernar genere SQL optimizado?

Hibernate debería permitirle proporcionar sugerencias sobre cómo construir la consulta más eficiente, pero sospecho que hay algo mal en la construcción de su tabla. ¿La relación se establece en las tablas? Es posible que Hibernate haya decidido que una consulta simple será más rápida que una combinación, especialmente si faltan índices, etc.


20

Prueba con:

select p from Person p left join FETCH p.address a where...

Me funciona de manera similar con JPA2 / EclipseLink, pero parece que esta característica también está presente en JPA1 :



2

para unirse puede hacer varias cosas (usando eclipselink)

  • en jpql puede hacer la búsqueda de unión izquierda

  • en una consulta con nombre, puede especificar una sugerencia de consulta

  • en TypedQuery puedes decir algo como

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • también hay una sugerencia de recuperación por lotes

    query.setHint("eclipselink.batch", "e.address");

ver

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html


1

Tuve exactamente este problema con la excepción de que la clase Person tenía una clase de clave incrustada. Mi propia solución fue unirlos en la consulta Y eliminar

@Fetch(FetchMode.JOIN)

Mi clase de identificación incrustada:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}

-1

Se me ocurren dos cosas.

Primero, ¿estás seguro de que te refieres a ManyToOne como dirección? Eso significa que varias personas tendrán la misma dirección. Si se edita para uno de ellos, se editará para todos. ¿Esa es tu intención? El 99% de las veces las direcciones son "privadas" (en el sentido de que pertenecen a una sola persona).

En segundo lugar, ¿tiene alguna otra relación ansiosa en la entidad Persona? Si mal no recuerdo, Hibernate solo puede manejar una relación ansiosa en una entidad, pero esa es posiblemente información desactualizada.

Digo eso porque su comprensión de cómo debería funcionar esto es esencialmente correcta desde donde estoy sentado.


Este es un ejemplo inventado que usa muchos a uno. La dirección de la persona puede no haber sido el mejor ejemplo. No veo ningún otro tipo de búsqueda ansiosa en mi código.
Steve Kuo

Entonces, mi sugerencia es reducir esto a un ejemplo simple que se ejecute y haga lo que está viendo y luego publique eso. Puede haber otras complicaciones en su modelo que estén provocando un comportamiento inesperado.
cletus

1
Ejecuté el código exactamente como aparece arriba y muestra dicho comportamiento.
Steve Kuo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.