java - vainilla - Diseño de la aplicación de carga diferida de Hibernate
vainilla lazy load (3)
Tiendo a utilizar Hibernate en combinación con Spring framework y sus capacidades de demarcación de transacciones declarativas (p. @Transactional ., @Transactional ).
Como todos sabemos, hibernate trata de ser lo menos invasivo y transparente posible, sin embargo, esto resulta un poco más desafiante cuando se emplean relaciones de lazy-loaded
lenta.
Veo una serie de alternativas de diseño con diferentes niveles de transparencia.
- Hacer que las relaciones no se carguen de manera diferida (por ejemplo,
fetchType=FetchType.EAGER)
- Esto viola la idea de carga lenta.
- Inicializar colecciones utilizando
Hibernate.initialize(proxyObj);
- Esto implica un acoplamiento relativamente alto con el DAO
- Aunque podemos definir una interfaz con
initialize
, no se garantiza que otras implementaciones proporcionen ningún equivalente.
- Agregue el comportamiento de la transacción a los propios objetos persistentes del
Model
(utilizando un proxy dinámico o@Transactional
)- No probé el enfoque de proxy dinámico, aunque nunca pareció lograr que @Transactional funcionara en los objetos persistentes. Probablemente debido a que hibernar es la operación en un proxy para estar con.
- Pérdida de control cuando las transacciones se están llevando a cabo
- Proporcione API floja / no
loadData()
, por ejemplo,loadData()
yloadDataWithDeps()
- Obliga a la aplicación a saber cuándo emplear qué rutina, de nuevo acoplamiento hermético
- Desbordamiento de método,
loadDataWithA()
, ....,loadDataWithX()
-
byId()
fuerza para dependencias, por ejemplo, solo proporcionando operacionesbyId()
- Requiere muchas rutinas no orientadas a objetos, por ejemplo,
findZzzById(zid)
, y luegogetYyyIds(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.
- Requiere muchas rutinas no orientadas a objetos, por ejemplo,
- Forma parte de la aplicación @Transactional en lugar de solo DAO
- Posibles consideraciones de las transacciones anidadas
- Requiere rutinas adaptadas para la gestión de transacciones (p. Ej., Bastante pequeñas)
- Pequeño impacto programático, aunque podría dar lugar a grandes transacciones
- Proporcione al DAO perfiles de búsqueda dinámicos, por ejemplo,
loadData(id, fetchProfile);
- Las aplicaciones deben saber qué perfil usar cuando
- Tipo de transacciones AOP, por ejemplo, operaciones de interceptación y realizar transacciones cuando sea necesario
- Requiere manipulación de código byte 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 trata de minimizar el impacto de lazy-loaded
relaciones lazy-loaded
en el diseño de su aplicación?
(Ah, y lo siento por WoT )
Como todos sabemos, hibernate intenta ser lo menos invasivo y lo más transparente posible
Yo diría que la suposición inicial es incorrecta. La persistencia de Transaparent 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 está cargando.
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, necesita expresar sus intenciones para Hibernar de alguna manera.
Desde este punto de vista, las soluciones que expresan estas intenciones explícitamente (a saber, 2, 4 y 7) parecen razonables y no adolecen de falta de transparencia.
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
foo
se carga y coloca en un mapa; - otro hilo toma este objeto del mapa y llama a
foo.getBar()
(algo que nunca se llamó antes y se evalúa de forma diferida); - ¡auge!
Entonces, para abordar esto, tenemos una serie de reglas:
- envolver las sesiones de la forma más transparente posible (por ejemplo,
OpenSessionInViewFilter
para webapps); - tienen una API común para subprocesos / grupos de subprocesos donde la vinculación / desvinculación de la sesión de db se realiza en algún lugar de la jerarquía (envuelto en
try/finally
), por lo que las subclases no tienen que pensar en ello; - al pasar objetos entre hilos, pase IDs en lugar de objetos. El hilo receptor puede cargar el objeto si es necesario;
- al almacenar objetos en caché, nunca almacene objetos en caché, excepto sus identificadores. Tenga un método abstracto en su DAO o clase de administrador para cargar el objeto desde la memoria caché de Hibernación de segundo nivel cuando conozca la ID. El costo de recuperar objetos del segundo nivel de la memoria caché de Hibernate es mucho más barato que ir a DB.
Esto, como puedes ver, de hecho no está cerca de ser no invasivo y transparente . Pero el costo aún es soportable, para compararlo con el precio que tendría que pagar por la carga ansiosa. El problema con esto último es que a veces conduce al efecto mariposa cuando se carga un solo objeto de referencia, 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 creo que puedo vivir con eso.
Un patrón muy común es usar OpenEntityManagerInViewFilter si está creando una aplicación web.
Si está creando un servicio, abriría la TX en el método público del servicio, en lugar de en los DAO, ya que con frecuencia un método requiere obtener o actualizar varias entidades.
Esto resolverá cualquier "excepción de carga diferida". Si necesita algo más avanzado para la optimización del rendimiento, creo que fetch profiles es el camino a seguir.