Diseño de aplicaciones de carga diferida de Hibernate

87

Tiendo a usar Hibernate en combinación con el marco Spring y sus capacidades de demarcación de transacciones declarativas (por ejemplo, @Transactional ).

Como todos sabemos, hibernate intenta ser lo más no invasivo y transparente posible, sin embargo, esto resulta un poco más desafiante cuando se emplean lazy-loadedrelaciones.


Veo una serie de alternativas de diseño con diferentes niveles de transparencia.

  1. Haga relaciones no cargadas de pereza (p. Ej., fetchType=FetchType.EAGER)
    • Esto viola toda la idea de carga diferida.
  2. Inicializar colecciones usando Hibernate.initialize(proxyObj);
    • Esto implica un acoplamiento relativamente alto al DAO
    • Aunque podemos definir una interfaz con initialize, no se garantiza que otras implementaciones proporcionen un equivalente.
  3. Agregue el comportamiento de la transacción a los Modelpropios objetos persistentes (utilizando un proxy dinámico o @Transactional)
    • No probé el enfoque de proxy dinámico, aunque nunca pareció que @Transactional funcionara en los objetos persistentes. Probablemente debido a que la hibernación es la operación en un proxy con el que estar.
    • Pérdida de control cuando las transacciones realmente se están llevando a cabo.
  4. Proporcionar API perezosa / no perezosa, por ejemplo, loadData()yloadDataWithDeps()
    • Obliga a la aplicación a saber cuándo emplear qué rutina, de nuevo acoplamiento estrecho
    • Desbordamiento del método,, loadDataWithA()....,loadDataWithX()
  5. Forzar la búsqueda de dependencias, por ejemplo, proporcionando solo byId()operaciones
    • Requiere muchas rutinas no orientadas a objetos, por ejemplo findZzzById(zid), y luego en getYyyIds(zid)lugar dez.getY()
    • Puede ser útil buscar cada objeto en una colección uno por uno si hay una gran sobrecarga de procesamiento entre las transacciones.
  6. Haga parte de la aplicación @Transactional en lugar de solo el DAO
    • Posibles consideraciones de transacciones anidadas
    • Requiere rutinas adaptadas para la gestión de transacciones (p. Ej., Suficientemente pequeñas)
    • Pequeño impacto programático, aunque puede resultar en grandes transacciones
  7. Proporcionar al DAO perfiles de búsqueda dinámicos , por ejemplo,loadData(id, fetchProfile);
    • Las aplicaciones deben saber qué perfil usar cuando
  8. Tipo de transacciones AoP, por ejemplo, interceptar operaciones y realizar transacciones cuando sea necesario
    • Requiere manipulación de código de bytes o uso de proxy
    • Pérdida de control cuando se realizan transacciones
    • Magia negra, como siempre :)

¿Perdí alguna opción?


¿Cuál es su enfoque preferido cuando intenta minimizar el impacto de las lazy-loadedrelaciones en el diseño de su aplicación?

(Oh, y lo siento por WoT )

Johan Sjöberg
fuente
ejemplo para la opción 2 y 5: m-hewedy.blogspot.ch/2010/03/…
Adrien Be
¿Podría proporcionar un ejemplo para la opción 4?
gradosightdc

Respuestas:

26

Como todos sabemos, hibernate intenta ser lo más no invasivo y transparente posible

Yo diría que la suposición inicial es incorrecta. La persistencia transaparente es un mito, ya que la aplicación siempre debe ocuparse del ciclo de vida de la entidad y del tamaño del gráfico de objetos que se carga.

Tenga en cuenta que Hibernate no puede leer pensamientos, por lo tanto, si sabe que necesita un conjunto particular de dependencias para una operación en particular, debe expresar sus intenciones de Hibernate de alguna manera.

Desde este punto de vista, las soluciones que expresan estas intenciones explícitamente (es decir, 2, 4 y 7) parecen razonables y no adolecen de la falta de transparencia.

axtavt
fuente
Tienes razón, por supuesto, lo más transparente posible solo funciona hasta ahora. Esas son algunas buenas opciones que eligió.
Johan Sjöberg
En mi humilde opinión: respuesta perfectamente correcta. De hecho, es un mito. Por cierto: mi voto sería para las opciones 4 y 7 (o alejarme del ORM en absoluto)
G. Demecki
7

No estoy seguro de qué problema (causado por la pereza) está insinuando, pero para mí el mayor dolor es evitar perder el contexto de la sesión en mis propios cachés de aplicaciones. Caso típico:

  • el objeto foose carga y se coloca en un mapa;
  • otro hilo toma este objeto del mapa y llama foo.getBar()(algo que nunca se llamó antes y es evaluado de forma diferida);
  • ¡auge!

Entonces, para abordar esto, tenemos una serie de reglas:

  • envolver sesiones de la forma más transparente posible (por ejemplo, OpenSessionInViewFilterpara aplicaciones web);
  • tener una API común para subprocesos / grupos de subprocesos donde la vinculación / desvinculación de la sesión de la base de datos se realiza en algún lugar alto en la jerarquía (envuelto try/finally) para que las subclases no tengan que pensar en ello;
  • al pasar objetos entre subprocesos, pase ID en lugar de los propios objetos. El subproceso receptor puede cargar el objeto si es necesario;
  • cuando almacene objetos en caché, nunca almacene objetos en caché sino sus identificadores. Tenga un método abstracto en su DAO o clase de administrador para cargar el objeto desde el caché de Hibernate de segundo nivel cuando sepa la ID. El costo de recuperar objetos del caché de Hibernate de segundo nivel sigue siendo mucho más económico que ir a DB.

Esto, como puede ver, de hecho no se acerca a lo no invasivo y transparente . Pero el costo sigue siendo soportable, en comparación con el precio que tendría que pagar por una carga ansiosa. El problema con este último es que a veces conduce al efecto mariposa cuando se carga un solo objeto referenciado, y mucho menos una colección de entidades. El consumo de memoria, el uso de la CPU y la latencia, por mencionar lo menos, también son mucho peores, así que supongo que puedo vivir con eso.

mindas
fuente
Gracias por su respuesta. La pérdida de transparencyes por obligar a la aplicación a preocuparse por cargar objetos perezosos. Si todo fue recuperado con entusiasmo, la aplicación podría ignorar por completo si los objetos se mantienen en una base de datos o no, ya Foo.getBar()que siempre tendrá éxito. > when passing objects between threads, pass IDs, sí, esto correspondería al # 5.
Johan Sjöberg
3

Un patrón muy común es usar OpenEntityManagerInViewFilter si está creando una aplicación web.

Si está creando un servicio, abriría el TX en el método público del servicio, en lugar de en los DAO, ya que muy a menudo un método requiere obtener o actualizar varias entidades.

Esto resolverá cualquier "excepción de carga diferida". Si necesita algo más avanzado para ajustar el rendimiento, creo que buscar perfiles es el camino a seguir.

Augusto
fuente
1
Supongo que quería decir: Un anti patrón muy común ... . Aunque estaría de acuerdo con abrir TX en el nivel de servicio, pero el uso de la OSIVsigue siendo un antipatrón y conduce a problemas muy serios como la incapacidad para lidiar con elegancia con excepciones o degradación del rendimiento. En resumen: en mi humilde opinión, OSIV es una solución tranquila, pero buena solo para proyectos de juguetes.
G. Demecki