java - namedquery - La búsqueda ansiosa de JPA no se une
jpa java se (8)
¿Qué controla exactamente la estrategia de búsqueda de JPA? No puedo detectar ninguna diferencia entre impaciente y perezoso. En ambos casos, JPA / Hibernate no se une automáticamente a las relaciones de muchos a uno.
Ejemplo: la persona tiene una sola dirección. Una dirección puede pertenecer a muchas personas. Las clases de entidades 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 utilizo la consulta JPA:
select p from Person p where ...
JPA / Hibernate genera una consulta SQL para seleccionar de la tabla Persona, 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 grandes conjuntos de resultados. 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. Según tengo entendido, al establecer el tipo de búsqueda de dirección en impaciente 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 explícitamente le digo 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 quede unida 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 here y here ) que FetchType
no afecta las consultas de JPA. Entonces en mi caso, explícitamente le dije que se uniera.
"mxc" es correcto. fetchType
simplemente 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.
Dos cosas se me ocurren.
Primero, ¿está seguro de que quiere decir ManyToOne para la dirección? Eso significa que varias personas tendrán la misma dirección. Si está editado para uno de ellos, se editará para todos ellos. ¿Esa es tu intención? El 99% de las direcciones de tiempo son "privadas" (en el sentido de que pertenecen a una sola persona).
En segundo lugar, ¿tiene alguna otra relación entusiasta en la entidad Persona? Si recuerdo correctamente, Hibernate solo puede manejar una relación impaciente en una entidad, pero posiblemente sea información desactualizada.
Lo digo porque su comprensión de cómo debería funcionar esto es esencialmente correcto desde donde estoy sentado.
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 instrucción fetch, la implementación real de sql depende del proveedor que está utilizando toplink / hibernate, etc.
Si configura fetchType=EAGER
Esto significa que el campo anotado se llena con sus valores al mismo tiempo que los otros campos en la entidad. Por lo tanto, si abre un entitymanager recupera sus objetos personales y luego cierra el entitymanager, posteriormente hacer una dirección de persona no dará lugar a una excepción de carga lenta.
Si configura fetchType=LAZY
el campo solo se llena cuando se accede a él. Si cerró el entitymanager para entonces se lanzará una excepción de carga lenta si hace una dirección de persona. Para cargar el campo, debe volver a colocar la entidad en el contexto de entitymangers con em.merge (), luego hacer el acceso de 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ó cada pedido de un cliente cuando quería obtener una lista de clientes, esta puede ser una operación costosa de la base de datos cuando solo busca el nombre del cliente y los detalles de contacto. Lo mejor es dejar el acceso db hasta más tarde.
Para la segunda parte de la pregunta: ¿cómo hibernar para generar SQL optimizado?
Hibernate debería permitirle dar pistas sobre cómo construir la consulta más eficiente, pero sospecho que hay algún problema con la construcción de su tabla. ¿La relación está establecida en las tablas? Hibernate puede haber decidido que una consulta simple será más rápida que una combinación, especialmente si faltan índices, etc.
JPA no proporciona ninguna especificación sobre la asignación de anotaciones para seleccionar la estrategia de búsqueda. En general, las entidades relacionadas se pueden obtener de cualquiera de las maneras que se detallan a continuación
- SELECT => una consulta para entidades raíz + una consulta para la entidad / colección asignada relacionada de cada entidad raíz = (n + 1) consultas
- SUBSELECT => una consulta para entidades raíz + segunda consulta para entidad relacionada / colección 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 asignada = 1 consulta
Entonces SELECT
y JOIN
son dos extremos y SUBSELECT
cae entre ellos. Uno puede elegir una estrategia adecuada basada en su modelo de dominio.
Por defecto, SELECT
es utilizado tanto por JPA / EclipseLink como por Hibernate. Esto puede ser anulado usando:
@Fetch(FetchMode.JOIN)
@Fetch(FetchMode.SUBSELECT)
en Hibernate. También permite establecer el modo SELECT
explícitamente usando @Fetch(FetchMode.SELECT)
que se puede sintonizar utilizando el tamaño del lote, por ejemplo @BatchSize(size=10)
.
Las anotaciones correspondientes en EclipseLink son:
@JoinFetch
@BatchFetch
Prueba con:
select p from Person p left join FETCH p.address a where...
Funciona para mí de forma similar con JPA2 / EclipseLink, pero parece que esta característica también está presente en JPA1 :
Si usa EclipseLink en lugar de Hibernate, puede optimizar sus consultas mediante "sugerencias de consulta". Vea este artículo de la Wiki de Eclipse: EclipseLink/Examples/JPA/QueryOptimization .
Hay un capítulo sobre "Lectura unida".
Tenía exactamente este problema con la excepción de que la clase Person tenía una clase de clave incrustada. Mi propia solución fue unirme a ellos 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();
}
}
para unirte puedes hacer varias cosas (usando eclipselink)
en jpql puede hacer la búsqueda de unirse a la 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 búsqueda por lotes
query.setHint("eclipselink.batch", "e.address");
ver
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html