2010-01-07 12 views
13

Estoy pensando en el diseño de tabla para una jerarquía de clases gestionada por Hibernate, y ciertamente la técnica de tabla por subclase me parece la más apropiada en un sentido general. Sin embargo, pensando en la lógica, tengo algunas preocupaciones sobre su rendimiento, especialmente a medida que aumenta el número de subclases.Eficiencia de la estrategia de herencia de tabla por subclase de Hibernate

para dar una muy breve (y clásico) ejemplo, digamos que usted tiene las siguientes clases:

captadores
public abstract class Animal { 
    int pkey; 
    String name; 
} 

public class Dog extends Animal { 
    long numSlippersChewed; // int is not large enough... 
} 

public class Cat extends Animal { 
    short miceCaught; // ... but here int is far bigger than required :-) 
} 

(estoy elidiendo y definidores y de hibernación asignaciones etc., a suponer que son el caso obvio básico).

Las tablas de la base de datos para estas entidades tienen sentido, obtienes una buena desnormalización, etc. Sin embargo, ¿qué consulta hace Hibernate para extraer un animal individual? No puedo pensar en al menos dos casos en que esto puede ocurrir:

  1. Algunos otra entidad que tenga una correspondencia uno-a-uno (o uno-a-muchos), tales como un campo de una clase Humanpet. Esto almacenaría el pkey, por lo que cuando Hibernate recupera un objeto Humano, también tendrá que buscar el objeto correspondiente Animal. Cuando se le presente la clave del animal, ¿qué consulta (es) utilizará Hibernate para extraer y separar los datos reales de Animal, dado que podría residir en las tablas Cat o Dog?
  2. HQL como from Animal where name='Rex' (supongamos que los nombres son únicos). Esto es similar al anterior ya que le permite identificar una fila en la tabla de superclase pero no sabe qué tabla de subclase debe inspeccionar para obtener más detalles. ¿HQL incluso le permite emitir una consulta from una clase abstracta? (El uso de elementos específicos de subclase funciona bien, por ejemplo, from Cat where miceCaught > 5).

Se me ocurren dos maneras en que esto podría hacerse en SQL y ninguna de las dos parece bonita. Una es ejecutar una consulta exists en cada tabla de subclase para el pkey dado y luego cargar desde la tabla que devolvió un hit. Alternativamente, Hibernate podría realizar una unión de consulta de unión horrible en todas las tablas, simulando esencialmente el esquema de tabla por jerarquía en que el conjunto de resultados incluiría atributos para todas las subclases posibles con las selecciones individuales de las tablas de subclase que devuelven null para los argumentos irrelevantes. Este último caso probablemente incluso necesitaría agregar una columna de discriminador sintético para que Hibernate supiera qué tabla de subclase realmente devolvió la fila y, por lo tanto, en qué clase de Java deberían analizarse.


Las cosas se ponen más peludo también si usted tiene subtipos de tipos concretos:

public class Greyhound extends Dog { 
    float lifetimeRacingWinnings; 
} 

Ahora para una tecla p animal determinado, puede haber filas válidos en los DogyGreyhound mesas, lo que significa que mi primer enfoque de chequear manualmente la clase que corresponde a un pkey se vuelve mucho más difícil.

La razón por la que estoy tan preocupado es que querré utilizar este enfoque en una jerarquía de clases con aproximadamente 70 clases con una cadena de anidación máxima de 4-5 niveles, por lo que realizar una consulta de unión en todo eso es es probable que tenga horrible rendimiento. ¿Tiene Hibernate algún truco en la manga para mantener este rendimiento relativamente bueno? ¿O cargar una referencia a una de estas clases por pkey va a tomar mucho tiempo?

+0

Quizás quieras ver http://stackoverflow.com/questions/2700680/table-per-subclass-inheritance-relationship-how-to-query-against-the-parent-clas –

+0

Por curiosidad, me pregunto si algunos otros sistemas de almacenamiento basados ​​en objetos JPA tienen el mismo problema (es decir, ObjectDB). En otras palabras, tal vez una base de datos relacional estándar no sea la mejor, especialmente porque tiene un gran gráfico de objetos. –

Respuesta

8

Encontrará que Hibernate escribe la consulta para un tipo de animal desconocido con una serie de declaraciones LEFT JOIN, una por subclase. Por lo tanto, la consulta se ralentizará a medida que aumente el número de subclases, e intentará devolver un conjunto de resultados cada vez más amplio. Entonces estás en lo cierto, no escala bien con las jerarquías de clase grandes.

Con HQL, sí puede consultar la subclase directamente y acceder a sus propiedades. Eso se representará con un solo INNER JOIN.

No he probado esto con múltiples niveles de herencia. Si lo anterior aún no lo ha desanimado, sugiérale que lo intente y vea: puede activar la salida de depuración de SQL para ver qué se envía a la base de datos o simplemente crear un perfil de su base de datos.

+0

Gracias. Consideré probarme pero, por alguna razón, no creía que valiera la pena sin un conjunto de datos realista para las pruebas de rendimiento, y no quería * empezar * a desarrollarme de esta manera con mis preocupaciones. Sin embargo, tiene razón en que un caso de prueba ficticio al menos me permitirá ver las técnicas utilizadas, y reducir esto de una pregunta de rendimiento de Hibernate a una pregunta de rendimiento SQL, que debería ser mucho más fácil de razonar. –

+0

En referencia a su tercer párrafo: como muestra el SQL I publicado, los múltiples niveles de herencia no parecen cambiar mucho. Hasta donde puedo decir, la única diferencia es el orden de las cláusulas en la sentencia 'case' usada para generar un discriminador sintético. Por lo tanto, desde una perspectiva de rendimiento esto debería ser idéntico. (Por supuesto, habrá más tablas para unir cuando concretamente instanciar una subclase, pero eso no me preocupa demasiado). –

+0

Marcando esto como aceptado ya que en la reflexión responde claramente la pregunta que hice, a pesar de que no proporciona la bala mágica que estaba esperando. –

3

Después de David M's helpful answer decidí lanzar juntos una prueba de esqueleto.

Creé una superclase abstracta, ADTestA, y 25 subclases concretas en una jerarquía de tres niveles (supongo que puedes adivinar sus nombres). Cada clase tiene un único campo entero con un nombre que corresponde a su letra, por ejemplo, la clase ADTestG tiene un único campo int g además del campo b que hereda de su padre inmediato ADTestB, y los campos pkey y a de la parte superior superclase abstracta de nivel.

emisión de la consulta HQL from ADTestA where pkey=1 dio como resultado el siguiente código SQL:

select adtesta0_.pkey as pkey0_, adtesta0_.a as a0_, adtesta0_1_.b as b1_, 
     adtesta0_2_.c as c2_, adtesta0_3_.d as d3_, adtesta0_4_.e as e4_, 
     adtesta0_5_.f as f5_, adtesta0_6_.g as g6_, adtesta0_7_.h as h7_, 
     adtesta0_8_.i as i8_, adtesta0_9_.j as j9_, adtesta0_10_.k as k10_, 
     adtesta0_11_.l as l11_, adtesta0_12_.m as m12_, adtesta0_13_.n as n13_, 
     adtesta0_14_.o as o14_, adtesta0_15_.p as p15_, adtesta0_16_.q as q16_, 
     adtesta0_17_.r as r17_, adtesta0_18_.s as s18_, adtesta0_19_.t as t19_, 
     adtesta0_20_.u as u20_, adtesta0_21_.v as v21_, adtesta0_22_.w as w22_, 
     adtesta0_23_.x as x23_, adtesta0_24_.y as y24_, adtesta0_25_.z as z25_, 
     case 
      when adtesta0_6_.pkey is not null then 6 
      when adtesta0_7_.pkey is not null then 7 
      when adtesta0_8_.pkey is not null then 8 
      when adtesta0_9_.pkey is not null then 9 
      when adtesta0_10_.pkey is not null then 10 
      when adtesta0_11_.pkey is not null then 11 
      when adtesta0_12_.pkey is not null then 12 
      when adtesta0_13_.pkey is not null then 13 
      when adtesta0_14_.pkey is not null then 14 
      when adtesta0_15_.pkey is not null then 15 
      when adtesta0_16_.pkey is not null then 16 
      when adtesta0_17_.pkey is not null then 17 
      when adtesta0_18_.pkey is not null then 18 
      when adtesta0_19_.pkey is not null then 19 
      when adtesta0_20_.pkey is not null then 20 
      when adtesta0_21_.pkey is not null then 21 
      when adtesta0_22_.pkey is not null then 22 
      when adtesta0_23_.pkey is not null then 23 
      when adtesta0_24_.pkey is not null then 24 
      when adtesta0_25_.pkey is not null then 25 
      when adtesta0_1_.pkey is not null then 1 
      when adtesta0_2_.pkey is not null then 2 
      when adtesta0_3_.pkey is not null then 3 
      when adtesta0_4_.pkey is not null then 4 
      when adtesta0_5_.pkey is not null then 5 
      when adtesta0_.pkey is not null then 0 
     end as clazz_ 
from ADTestA adtesta0_ 
      left outer join ADTestB adtesta0_1_ on adtesta0_.pkey=adtesta0_1_.pkey 
      left outer join ADTestC adtesta0_2_ on adtesta0_.pkey=adtesta0_2_.pkey 
      left outer join ADTestD adtesta0_3_ on adtesta0_.pkey=adtesta0_3_.pkey 
      left outer join ADTestE adtesta0_4_ on adtesta0_.pkey=adtesta0_4_.pkey 
      left outer join ADTestF adtesta0_5_ on adtesta0_.pkey=adtesta0_5_.pkey 
      left outer join ADTestG adtesta0_6_ on adtesta0_.pkey=adtesta0_6_.pkey 
      left outer join ADTestH adtesta0_7_ on adtesta0_.pkey=adtesta0_7_.pkey 
      left outer join ADTestI adtesta0_8_ on adtesta0_.pkey=adtesta0_8_.pkey 
      left outer join ADTestJ adtesta0_9_ on adtesta0_.pkey=adtesta0_9_.pkey 
      left outer join ADTestK adtesta0_10_ on adtesta0_.pkey=adtesta0_10_.pkey 
      left outer join ADTestL adtesta0_11_ on adtesta0_.pkey=adtesta0_11_.pkey 
      left outer join ADTestM adtesta0_12_ on adtesta0_.pkey=adtesta0_12_.pkey 
      left outer join ADTestN adtesta0_13_ on adtesta0_.pkey=adtesta0_13_.pkey 
      left outer join ADTestO adtesta0_14_ on adtesta0_.pkey=adtesta0_14_.pkey 
      left outer join ADTestP adtesta0_15_ on adtesta0_.pkey=adtesta0_15_.pkey 
      left outer join ADTestQ adtesta0_16_ on adtesta0_.pkey=adtesta0_16_.pkey 
      left outer join ADTestR adtesta0_17_ on adtesta0_.pkey=adtesta0_17_.pkey 
      left outer join ADTestS adtesta0_18_ on adtesta0_.pkey=adtesta0_18_.pkey 
      left outer join ADTestT adtesta0_19_ on adtesta0_.pkey=adtesta0_19_.pkey 
      left outer join ADTestU adtesta0_20_ on adtesta0_.pkey=adtesta0_20_.pkey 
      left outer join ADTestV adtesta0_21_ on adtesta0_.pkey=adtesta0_21_.pkey 
      left outer join ADTestW adtesta0_22_ on adtesta0_.pkey=adtesta0_22_.pkey 
      left outer join ADTestX adtesta0_23_ on adtesta0_.pkey=adtesta0_23_.pkey 
      left outer join ADTestY adtesta0_24_ on adtesta0_.pkey=adtesta0_24_.pkey 
      left outer join ADTestZ adtesta0_25_ on adtesta0_.pkey=adtesta0_25_.pkey 
where adtesta0_.pkey=1 

Eso no es muy bonita, y que corresponde a la simulación efectiva de la tabla por jerarquía que esperaba que podrían evitarse.

Parece que este tipo de consultas van a ser muy costosas. Voy a pensar en la frecuencia con la que se necesitarían (en comparación, por ejemplo, para saber que quiero una instancia de ADTestP y solicitar una de ellas desde el principio, que solo se une en las tablas principales obligatorias). Sin embargo, tengo la sensación de que esto será inevitable con referencias de otras entidades; en otras palabras, un mapeo uno a uno desde un campo de tipo ADTestA siempre implicará exactamente este tipo de búsqueda.

(Por otro lado, las estrategias alternativas tampoco son un faro de esperanza; ir a la ruta de tabla por jerarquía y tener literalmente cientos de columnas en una sola tabla tampoco suena muy eficiente ...)

+0

+1 para retroalimentación - gracias. –

1

Siempre que acceda a su base de datos solo a través de Hibernate y no tenga datos importantes o esté listo para escribir un pequeño script de migración, podrá tomar la decisión sobre la tabla por subclase/jerarquía bastante tarde en su desarrollo proceso. Esa es la belleza de un ORM, abstrae la estructura de la base de datos ...

Por otro lado, soy un gran admirador de "prefiero la composición sobre la herencia" (Prefer composition over inheritance?) y soy bastante dudoso que un modelo con 70 las clases de más de 4 a 5 niveles no se pueden simplificar ... pero te dejaré pensar por ti mismo sobre eso, después de todo, no sé qué prueba estás tratando de resolver.

+0

Desafortunadamente, las tablas se * accederán * principalmente * a través de Hibernate, pero el diseño debe ser al menos cuerdo para que otras herramientas que no sean Java accedan ocasionalmente. Y puedo estar confundiendo la relevancia de las preocupaciones aquí, pero la jerarquía de clases se está refabricando al mismo tiempo que se presenta una nueva tabla, con el desempeño de DB como uno de los principales motivadores. Estoy de acuerdo con el tema de la abstracción hasta cierto punto, pero como Joel escribió, las abstracciones se filtran; si no es posible hacer esto de manera eficiente en Hibernate, lo ideal es que tenga que descubrirlo lo antes posible. –

+0

Ah, y consideré escribir un descargo de responsabilidad sobre el número de subclases que son legítimas porque pensé que alguien lo destacaría como un olor. :-) Continuando con la analogía con Animal, estoy escribiendo código para algo parecido a un zoológico o veterinario, así que legítimamente tenemos un 'Animal'>' Vertebrado'> 'Mamífero'>' Carnívoro'> 'Canino'>' Perro' tipo jerarquía, para muchos animales diferentes (y sí, hay un comportamiento útil en cada nivel de la jerarquía). El código de Java es mucho más agradable y más limpio con esta estructura de clases que con la casi plana que reemplaza. –

Cuestiones relacionadas