java - La relación One-To-Many obtiene objetos duplicados sin usar "distinct". ¿Por qué?
hibernate hql (1)
Tengo 2 clases en una relación uno a muchos y una consulta HQL que es un poco extraña. Incluso si he leído algunas preguntas ya publicadas, no me parece claro.
Class Department{
@OneToMany(fetch=FetchType.EAGER, mappedBy="department")
Set<Employee> employees;
}
Class Employee{
@ManyToOne
@JoinColumn(name="id_department")
Department department;
}
Cuando uso la siguiente consulta obtengo duplicados de objetos del Departamento:
session.createQuery("select dep from Department as dep left join dep.employees");
Por lo tanto, tengo que usar distinct:
session.createQuery("select distinct dep from Department as dep left join dep.employees");
¿Este comportamiento es esperado? Considero esto inusual, ya que lo comparo con SQL.
Esta pregunta se explica detalladamente en las preguntas frecuentes de Hibernate :
En primer lugar, debe comprender SQL y cómo funcionan las UNIONES EXTERNAS en SQL. Si no comprende y comprende completamente las uniones externas en SQL, no continúe leyendo este elemento de Preguntas frecuentes, sino consulte un manual de SQL o un tutorial. De lo contrario, no comprenderá la siguiente explicación y se quejará de este comportamiento en el foro de Hibernate. Ejemplos típicos que pueden devolver referencias duplicadas del mismo objeto Order:
List result = session.createCriteria(Order.class)
.setFetchMode("lineItems", FetchMode.JOIN)
.list();
<class name="Order">
<set name="lineItems" fetch="join">
...
</class>
List result = session.createCriteria(Order.class)
.list();
List result = session.createQuery("select o from Order o left join fetch o.lineItems").list();
Todos estos ejemplos producen la misma declaración SQL:
SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID
¿Quieres saber por qué los duplicados están ahí? Mire el conjunto de resultados de SQL, Hibernate no oculta estos duplicados en el lado izquierdo del resultado externo unido pero devuelve todos los duplicados de la tabla de conducción. Si tiene 5 pedidos en la base de datos y cada orden tiene 3 líneas de pedido, el conjunto de resultados tendrá 15 filas. La lista de resultados de Java de estas consultas tendrá 15 elementos, todos de tipo Order. Solo 5 instancias de pedido serán creadas por Hibernate, pero los duplicados del conjunto de resultados de SQL se conservan como referencias duplicadas a estas 5 instancias. Si no entiende esta última oración, necesita leer sobre Java y la diferencia entre una instancia en el montón de Java y una referencia a dicha instancia. (¿Por qué una combinación externa izquierda? Si tuviera una orden adicional sin líneas de pedido, el conjunto de resultados sería 16 filas con NULL llenando el lado derecho, donde los datos de línea de pedido son para otra orden. Desea pedidos incluso si no tienen líneas de pedido, ¿verdad? Si no, utilice una búsqueda de combinación interna en su HQL).
Hibernate no filtra estas referencias duplicadas por defecto. Algunas personas (no tú) realmente quieren esto. ¿Cómo puedes filtrarlos? Me gusta esto:
Collection result = new LinkedHashSet( session.create*(...).list() );
Un LinkedHashSet filtra las referencias duplicadas (es un conjunto) y preserva el orden de inserción (orden de los elementos en su resultado). Eso fue muy fácil, así que puedes hacerlo de muchas maneras diferentes y más difíciles:
List result = session.createCriteria(Order.class)
.setFetchMode("lineItems", FetchMode.JOIN)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
<class name="Order">
...
<set name="lineItems" fetch="join">
List result = session.createCriteria(Order.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
List result = session.createQuery("select o from Order o left join fetch o.lineItems")
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) // Yes, really!
.list();
List result = session.createQuery("select distinct o from Order o left join fetch o.lineItems").list();
El último es especial. Parece que está utilizando la palabra clave SQL DISTINCT aquí. Por supuesto, esto no es SQL, esto es HQL. Este distinto es solo un atajo para el transformador de resultados, en este caso. Sí, en otros casos, un HQL distinto se traducirá directamente en SQL DISTINCT. No en este caso: no puede filtrar los duplicados en el nivel de SQL, la naturaleza misma de un producto / combinación lo prohíbe; quiere los duplicados o no obtiene todos los datos que necesita. Todo este filtrado de duplicados ocurre en la memoria, cuando el conjunto de resultados se organiza en objetos. También debería ser obvio por qué las operaciones de "límite" basadas en filas de resultados, tales como setFirstResult (5) y setMaxResults (10) no funcionan con este tipo de consultas de búsqueda impacientes. Si limita el conjunto de resultados a un cierto número de filas, corta los datos aleatoriamente. Un día, Hibernate podría ser lo suficientemente inteligente como para saber que si llama a setFirstResult () o setMaxResults () no debería usar un join, sino un segundo SQL SELECT. Pruébalo, tu versión de Hibernate ya podría ser lo suficientemente inteligente. Si no es así, escriba dos consultas, una para limitar cosas y la otra para buscar con entusiasmo. ¿Desea saber por qué el ejemplo con la consulta Criteria no omitió la configuración fetch = "join" en la asignación, pero a HQL no le importó? Lea el siguiente elemento de Preguntas frecuentes.