tutorial para mapeo español descargar curso con anotaciones java sql repository-pattern mybatis jooq

java - mapeo - Reemplazar un ORM completo(JPA/Hibernate) por una solución más liviana: ¿Patrones recomendados para cargar/guardar?



hibernate tutorial español (7)

Advertencia: este es otro enchufe desvergonzado de un autor del proyecto.

Consulte JSimpleDB y vea si cumple con sus criterios de simplicidad frente a potencia. Este es un nuevo proyecto nacido de más de 10 años de frustración tratando de lidiar con la persistencia de Java a través de SQL y ORM.

Funciona sobre cualquier base de datos que pueda funcionar como almacén de clave / valor, por ejemplo, FoundationDB (o cualquier base de datos SQL, aunque todo se atascará en una única tabla de clave / valor).

Estoy desarrollando una nueva aplicación web Java y estoy explorando nuevas formas (¡nuevas para mí!) De persistir en los datos. En su mayoría tengo experiencia con JPA e Hibernate pero, a excepción de casos simples, creo que este tipo de ORM completo puede volverse bastante complejo. Además, no me gusta trabajar tanto con ellos. Estoy buscando una nueva solución, probablemente más cerca de SQL.

Las soluciones que estoy investigando actualmente:

Pero hay dos casos de uso que me preocupan con esas soluciones, en comparación con Hibernate. Me gustaría saber cuáles son los patrones recomendados para esos casos de uso.

Caso de uso 1: Obtener una entidad y acceder a algunas de sus entidades asociadas de hijos y nietos.

  • Digamos que tengo una entidad Person .
    • Esta Person tiene una entidad de Address asociada.
      • Esta Address tiene una entidad de City asociada.
        • Esta entidad de la City tiene una propiedad de name .

La ruta completa para acceder al nombre de la ciudad, comenzando por la entidad persona, sería:

person.address.city.name

Ahora, digamos que cargué la entidad Persona desde un PersonService , con este método:

public Person findPersonById(long id) { // ... }

Utilizando Hibernate, las entidades asociadas a la Person podrían cargarse de forma perezosa, a pedido, de modo que sería posible acceder a person.address.city.name y asegurarme de tener acceso a esta propiedad (siempre y cuando todas las entidades de esa cadena no son nulables).

Pero usar cualquiera de las 3 soluciones que estoy investigando es más complicado. Con esas soluciones, ¿cuáles son los patrones recomendados para encargarse de este caso de uso? Upfront, veo 3 patrones posibles:

  1. Todas las entidades de hijos y nietos asociadas requeridas podrían cargarse ansiosamente por la consulta SQL utilizada.

    Pero el problema que veo con esta solución es que puede haber algún otro código que necesite acceder a otras rutas de entidades / propiedades desde la entidad Person . Por ejemplo, tal vez algún código necesite acceso a person.job.salary.currency . Si quiero reutilizar el método findPersonById() que ya tengo, ¡la consulta SQL tendrá que cargar más información! No solo la entidad asociada address->city sino también la entidad job->salary asociada.

    Ahora, ¿qué pasa si hay otros 10 lugares que necesitan acceder a otra información a partir de la entidad persona? ¿Debo siempre cargar ansiosamente toda la información potencialmente requerida? ¿O quizás tenga 12 diferentes métodos de servicio para cargar una entidad persona? :

    findPersonById_simple(long id) findPersonById_withAdressCity(long id) findPersonById_withJob(long id) findPersonById_withAdressCityAndJob(long id) ...

    Pero cada vez que use una entidad Person , tendré que saber qué se ha cargado y qué no ... Podría ser bastante engorroso, ¿verdad?

  2. En el getAddress() getter de la entidad Person , ¿podría haber una verificación para ver si la dirección ya se ha cargado y, si no, cargarla de forma lenta? Es este un patrón de uso frecuente en aplicaciones de la vida real?

  3. ¿Hay otros patrones que se puedan usar para asegurar que pueda acceder a las entidades / propiedades que necesito de un Modelo cargado?

Caso de uso 2: guardar una entidad y asegurarse de que sus entidades asociadas y modificadas también se guarden.

Quiero poder guardar una entidad Person utilizando el método de este PersonService :

public void savePerson(Person person) { // ... }

Si tengo una entidad Person y cambio person.address.city.name a otra cosa, ¿cómo puedo asegurarme de que las modificaciones de la entidad City mantendrán cuando guarde la Person ? Usando Hibernate, puede ser fácil conectar en cascada la operación de salvar a las entidades asociadas. ¿Qué pasa con las soluciones que estoy investigando?

  1. ¿Debo usar algún tipo de indicador sucio para saber qué entidades asociadas también tienen que guardarse cuando guardo a la persona?

  2. ¿Hay otros patrones conocidos útiles para tratar este caso de uso?

Actualización : hay una discusión sobre esta pregunta en el foro de JOOQ.


Como desea una biblioteca simple y liviana y usar SQL, puedo sugerirle que eche un vistazo a fjorm . Le permite usar POJOs y operaciones CRUD sin mucho esfuerzo.

Descargo de responsabilidad: soy un autor del proyecto.


Este tipo de problema es típico cuando no se utiliza un ORM real, y no hay una bala de plata. Un enfoque de diseño simple que funcionó para mí para una aplicación web (no muy grande) con iBatis (myBatis), es usar dos capas para la persistencia:

  • Una capa tonta de bajo nivel: cada tabla tiene su clase Java (POJO o DTO), con campos que se correlacionan directamente con las columnas de la tabla . Supongamos que tenemos una tabla PERSON con un campo ADDRESS_ID que apunta a una tabla ADRESS ; entonces, tendríamos una clase PersonDb , con solo un campo addressId (integer); no tenemos el método personDb.getAdress() , solo el simple personDb.getAdressId() . Estas clases de Java son, por lo tanto, bastante tontas (no saben sobre la persistencia o sobre las clases relacionadas). Una clase correspondiente de PersonDao sabe cómo cargar / persistir este objeto. Esta capa es fácil de crear y mantener con herramientas como iBatis + iBator (o MyBatis + MYBatisGenerator ).

  • Una capa de nivel superior que contiene objetos de dominio enriquecido : cada uno de estos es típicamente un gráfico de los POJO anteriores. Estas clases también tienen la inteligencia para cargar / guardar el gráfico (quizás perezosamente, quizás con algunas banderas sucias), llamando a los respectivos DAO. Sin embargo, lo importante es que estos objetos de dominio enriquecido no se correlacionan uno a uno con los objetos de POJO (o tablas de base de datos), sino con casos de uso de dominio . El "tamaño" de cada gráfico se determina (no crece indefinidamente) y se usa desde afuera como una clase en particular. Por lo tanto, no es que tenga una clase de Person rica (con algún gráfico indeterminado de objetos relacionados) que se usa en varios casos de uso o métodos de servicio; en cambio, tiene varias clases enriquecidas, PersonWithAddreses , PersonWithAllData ... cada una envuelve un gráfico particular bien limitado, con su propia lógica de persistencia. Esto puede parecer ineficiente o torpe, y en algún contexto podría ser, pero sucede a menudo que los casos de uso cuando se necesita guardar un gráfico completo de objetos son realmente limitados.

  • Además, para cosas como informes tabulares, (SELECCIONES específicas que devuelven un montón de columnas para mostrar) no usarías los anteriores, sino POJOs rectos y tontos (quizás incluso Mapas)

Ver mi respuesta relacionada here


La respuesta a sus muchas preguntas es simple. Tienes tres opciones.

  1. Utilice una de las tres herramientas centradas en SQL que ha mencionado ( MyBatis , JOOQ , DbUtils ). Esto significa que debe dejar de pensar en términos de su modelo de dominio OO y mapeo relacional de objetos (es decir, entidades y carga diferida). SQL se trata de datos relacionales y RBDMS son bastante buenos para calcular los planes de ejecución para "obtener con entusiasmo" el resultado de varias combinaciones. Por lo general, no hay mucha necesidad de almacenamiento en caché prematuro, y si necesita almacenar en caché el elemento de datos ocasionales, puede usar algo como EhCache

  2. No use ninguna de esas herramientas centradas en SQL y quédese con Hibernate / JPA. Porque incluso si dijeras que no te gusta Hibernate, estás "pensando en Hibernate". Hibernate es muy bueno para persistir gráficos de objetos en la base de datos. Ninguna de esas herramientas puede forzarse a funcionar como Hibernate, porque su misión es otra cosa. Su misión es operar en SQL.

  3. Vaya de una manera completamente diferente y elija no usar un modelo de datos relacionales. Otros modelos de datos (por ejemplo, gráficos) pueden ser más adecuados para usted. Estoy poniendo esto como una tercera opción, porque es posible que no tenga esa opción, y no tengo mucha experiencia personal con modelos alternativos.

Tenga en cuenta que su pregunta no fue específicamente sobre jOOQ. No obstante, con jOOQ, puede externalizar la asignación de resultados de consultas planas (producidas a partir de fuentes de tablas unidas) a gráficos de objetos a través de herramientas externas como ModelMapper . Hay un hilo continuo en curso sobre tal integración en el Grupo de Usuarios de ModelMapper .

(descargo de responsabilidad: yo trabajo para la compañía detrás de jOOQ)


Los últimos diez años estuve utilizando JDBC, EJB entity beans, Hibernate, GORM y finalmente JPA (en este orden). Para mi proyecto actual, he vuelto al uso de JDBC simple, porque el énfasis está en el rendimiento. Por lo tanto yo quería

  • Control total en la generación de sentencias de SQL: poder pasar una declaración a los sintonizadores de rendimiento de DB y volver a poner la versión optimizada en el programa
  • Control total sobre el número de sentencias SQL que se envían a la base de datos
  • Procedimientos almacenados (disparadores), funciones almacenadas (para cálculos complejos en consultas SQL)
  • Para poder usar todas las funciones de SQL disponibles sin restricciones (consultas recursivas con CTE, funciones agregadas de ventanas, ...)

El modelo de datos se define en un diccionario de datos; utilizando un enfoque basado en modelos, un generador crea clases auxiliares, scripts DDL, etc. La mayoría de las operaciones en la base de datos son de solo lectura; solo unos pocos casos de uso escriben.

Pregunta 1: Obtener niños

El sistema se basa en casos de uso, y tenemos una declaración de SQL dedicada para obtener todos los datos para un caso / solicitud de uso dado. Algunas de las sentencias de SQL son mayores de 20 kb, se unen, calculan usando funciones almacenadas escritas en Java / Scala, ordenan, paginan etc. de forma que el resultado se mapea directamente en un objeto de transferencia de datos que a su vez se alimenta a la vista (sin procesamiento adicional en la capa de aplicación). Como consecuencia, el objeto de transferencia de datos también es específico del caso de uso. Solo contiene los datos para el caso de uso dado (nada más, nada menos).

Como el conjunto de resultados ya está "completamente unido", no hay necesidad de buscar flojos / ansiosos, etc. El objeto de transferencia de datos está completo. No se necesita un caché (el caché de la base de datos está bien); la excepción: si el conjunto de resultados es grande (alrededor de 50 000 filas), el objeto de transferencia de datos se utiliza como un valor de caché.

Pregunta 2: Ahorro

Después de que el controlador haya fusionado los cambios desde la GUI, nuevamente hay un objeto específico que contiene los datos: Básicamente las filas con un estado (nuevo, eliminado, modificado, ...) en una jerarquía. Es una iteración manual para guardar los datos en la jerarquía: los datos nuevos o modificados se conservan utilizando algunas clases auxiliares con comandos de insert o update SQL. En cuanto a los elementos eliminados, esto se optimiza en eliminaciones en cascada (PostgreSql). Si se van a eliminar varias filas, esto se optimiza en una sola delete ... where id in ... declaración delete ... where id in ...

De nuevo, esto es específico del caso de uso, por lo que no se trata de una aproximación general. Necesita más líneas de código, pero estas son las líneas que contienen las optimizaciones.

Las experiencias hasta el momento

  • No se debe subestimar el esfuerzo por aprender Hibernate o JPA. Se debe considerar el tiempo dedicado a la configuración de las memorias caché, la invalidación de la caché en un clúster, la búsqueda ansiosa / diferida y el ajuste también. La migración a otra versión principal de Hibernate no es solo una recopilación.
  • Uno no debe sobreestimar el esfuerzo para construir una aplicación sin ORM.
  • Es más sencillo usar SQL directamente: estar cerca de SQL (como HQL, JPQL) no es lo mismo, especialmente si hablas con tu sintonizador de rendimiento DB.
  • Los servidores SQL son increíblemente rápidos cuando se ejecutan consultas largas y complejas, especialmente si se combinan con funciones almacenadas escritas en Scala
  • Incluso con las sentencias SQL específicas de casos de uso, el tamaño del código es más pequeño: los conjuntos de resultados "Completamente unidos" ahorran muchas líneas en la capa de la aplicación.

Información relacionada:

Actualización: Interfaces

Si hay una entidad Person en el modelo de datos lógicos, hay una clase para persistir una entidad Person (para operaciones CRUD), al igual que una entidad JPA.

Pero el problema es que no existe una sola entidad de Person desde la perspectiva de caso de uso / consulta / lógica comercial: cada método de servicio define su propia noción de Person , y solo contiene exactamente los valores requeridos por ese caso de uso. Nada más y nada menos. Usamos Scala, por lo que la definición y el uso de muchas clases pequeñas es muy eficiente (no se requiere código de placa de caldera setter / getter).

Ejemplo:

class GlobalPersonUtils { public X doSomeProcessingOnAPerson( Person person, PersonAddress personAddress, PersonJob personJob, Set<Person> personFriends, ...) }

es reemplazado por

class Person { List addresses = ... public X doSomeProcessingOnAPerson(...) } class Dto { List persons = ... public X doSomeProcessingOnAllPersons() public List getPersons() }

utilizando Persons específicas de casos de uso, Adresses , etc. En este caso, la Person ya agrega todos los datos relevantes. Requiere más clases, pero no es necesario pasar por las entidades JPA.

Tenga en cuenta que este proceso es de solo lectura, la vista utiliza los resultados. Ejemplo: obtener instancias de City distintas de la lista de una persona.

Si se modifican los datos, este es otro caso de uso: si se cambia la ciudad de una persona, se procesa con un método de servicio diferente y la persona vuelve a buscarse desde la base de datos.


Parece que el problema central en la pregunta es con el modelo relacional en sí mismo. De lo que se ha descrito, una base de datos de gráficos mapeará el dominio del problema muy claramente. Como alternativa, las tiendas de documentos son otra forma de abordar el problema porque, si bien la impedancia sigue presente, los documentos en general son más fáciles de razonar que los conjuntos. Por supuesto, cualquier enfoque tendrá sus propios caprichos para atender.


Enfoques de persistencia

El espectro de soluciones desde simple / básico hasta sofisticado / rico es:

  • SQL / JDBC - SQL de código duro dentro de los objetos
  • Marco basado en SQL (por ejemplo, jOOQ, MyBatis) - Patrón de registro activo (el objeto general separado representa datos de fila y maneja SQL)
  • ORM-Framework (por ejemplo, Hibernate, EclipseLink, DataNucleus) - Patrón de Data Mapper (Objeto por Entidad) más Patrón de Unidad de Trabajo (Persistence Context / Entity Manager)

Usted busca implementar uno de los dos primeros niveles. Eso significa cambiar el enfoque del modelo de objetos hacia SQL. Pero su pregunta requiere casos de uso que involucren el modelo de objeto que se mapea a SQL (es decir, comportamiento ORM). Desea agregar funcionalidad desde el tercer nivel a la funcionalidad de uno de los dos primeros niveles.

Podríamos intentar implementar este comportamiento dentro de un registro activo. Pero esto necesitaría metadatos ricos para adjuntar a cada instancia de Active Record: la entidad real involucrada, sus relaciones con otras entidades, la configuración de carga diferida, la configuración de actualización en cascada. Esto lo convertiría efectivamente en un objeto de entidad mapeado en la clandestinidad. Además, jOOQ y MyBatis no hacen esto para los casos de uso 1 y 2.

¿Cómo lograr tus peticiones?

Implemente un comportamiento estrecho de ORM directamente en sus objetos, como una pequeña capa personalizada sobre su estructura o SQL / JDBC sin formato.

Caso de uso 1: Almacenar metadatos para cada relación de objeto de entidad: (i) si la relación debe estar cargada de manera diferida (nivel de clase) y (ii) si se ha producido una carga lenta (nivel de objeto). Luego, en el método getter, usa estos indicadores para determinar si se realiza la carga diferida y realmente se hace.

Caso de uso 2: similar al caso de uso 1: hágalo usted mismo. Almacene una bandera sucia dentro de cada entidad. Contra cada relación de objeto de entidad, almacene un indicador que describa si el guardado debería ser en cascada. Luego, cuando se guarda una entidad, visita recursivamente cada relación de "guardar en cascada". Escribe cualquier entidad sucia descubierta.

Patrones

Pros

  • Las llamadas al marco SQL son simples.

Contras

  • Tus objetos se vuelven más complicados. Eche un vistazo al código de los casos de uso 1 y 2 dentro de un producto de código abierto. No es trivial
  • Falta de soporte para Object Model. Si está utilizando el modelo de objetos en Java para su dominio, tendrá menos soporte para las operaciones de datos.
  • Riesgo de fluencia y antipatrón de alcance: la funcionalidad que falta más arriba es la punta del iceberg. Puede terminar haciendo Reinvent the Wheel & Infrastructure Bloat en Business Logic.
  • Educación y mantenimiento en una solución no estándar. JPA, JDBC y SQL son estándares. Otros marcos o soluciones personalizadas no lo son.

¿¿¿Vale la pena???

Esta solución funciona bien si tiene requisitos de manejo de datos bastante simples y un modelo de datos con un número menor de entidades:

  • Si es así, genial! Haz arriba.
  • De lo contrario, esta solución no es adecuada y representa un ahorro de esfuerzo falso, es decir, acabará tardando más tiempo y será más complicado que utilizar un ORM. En ese caso, eche otro vistazo a JPA: podría ser más simple de lo que cree y admite ORM para CRUD más SQL sin formato para consultas complicadas :-).