Las muchas razones por las que debe ejecutar sus consultas jOOQ con jOOQ

Las muchas razones por las que debe ejecutar sus consultas jOOQ con jOOQ

Anteriormente en este blog, escribí un artículo explicando por qué debería usar el generador de código de jOOQ, a pesar de poder usar jOOQ sin él. Del mismo modo, respondí muchas preguntas de jOOQ en Stack Overflow, donde alguien usó jOOQ para crear una consulta, pero luego la ejecutó en otro lugar, incluso en:

  • APLICACIÓN
  • JDBC/R2DBC
  • JdbcTemplate (por Spring)
  • Etc.

jOOQ en sí mismo no tiene opinión y trata de adaptarse a todos los casos de uso posibles. Cada DOQ Query puede representar su SQL usando Query.getSQL()y producir valores vinculantes con Query.getBindValues()así que, en principio, ejecutar consultas jOOQ en otro lugar es completamente posible.

Algunos casos de uso válidos para hacer esto:

  • Está utilizando jOOQ solo para 2-3 consultas dinámicas en una aplicación basada en JPA, y necesita buscar entidades (no DTO) con esas consultas. Un ejemplo del manual, aquí.

    (Si está utilizando jOOQ para toneladas de consultas, probablemente comenzará a preguntarse si todavía necesita entidades en primer lugar).

Eso es casi todo. A inválido el caso de uso es:

  • Desea migrar lentamente al uso de jOOQ porque todo lo demás todavía usa JdbcTemplate, por ejemplo. Explicaré más adelante por qué este no es un buen caso de uso para extraer SQL de jOOQ.

En el siguiente artículo, quiero mostrar con un ejemplo las muchas ventajas de ejecutar consultas con jOOQ y, por lo tanto, por qué debería comenzar a usar jOOQ.

Este artículo trata de omitir todos los beneficios de edificio una consulta con jOOQ, suponiendo que ya haya decidido que jOOQ es la opción correcta para la creación de consultas.

Índice
  1. tipo de seguridad
  2. Cartografía
  3. emulaciones de tiempo de ejecución
  4. Tipos definidos por el usuario
  5. Procedimientos almacenados
  6. Recuperación de valores de identidad
  7. CRUD simple
  8. Importación y exportación de datos
  9. Mejores valores predeterminados
  10. Mucho más
  11. Prácticamente ningún beneficio de correr fuera de jOOQ
  12. Conclusión
    1. Así:

tipo de seguridad

Una de las principales ventajas de jOOQ es su seguridad de tipos tanto al escribir SQL como al mantenerlo. Mucho de esto se hace usando DSL y generación de código de jOOQ, pero eso no es todo. También puede beneficiarse de la seguridad de tipos al realizar consultas con jOOQ. Por ejemplo, aquí hay una consulta cuyo tipo recupera de forma segura una colección SQL anidada en un Java Map:

// This is the target data type
record Film(
    String title,
    Map<LocalDate, BigDecimal> revenue
) {}

// This query is entirely type safe. Change it, it won't compile anymore
List<Film> result =
ctx.select(
        FILM.TITLE,
        multiset(
            select(
                PAYMENT.PAYMENT_DATE.cast(LOCALDATE), 
                sum(PAYMENT.AMOUNT))
            .from(PAYMENT)
            .where(PAYMENT.rental().inventory().FILM_ID
                .eq(FILM.FILM_ID))
            .groupBy(PAYMENT.PAYMENT_DATE.cast(LOCALDATE))
            .orderBy(PAYMENT.PAYMENT_DATE.cast(LOCALDATE))
        )
        // Convert Field<Result<Record2<LocalDate, BigDecimal>>>
        // to Field<Map<LocalDate, BigDecimal>>
        .convertFrom(r -> r.collect(Records.intoMap())
   )
   .from(FILM)
   .orderBy(FILM.TITLE)
 
   // Convert Record2<String, Map<LocalDate, BigDecimal>>
   // to List<Film>
   .fetch(Records.mapping(Film::new))

Nuevamente, la construcción de la consulta ya es segura y está bien. Pero mucho más que eso, el final fetch(mapping(Film::new)) ¡la llamada también es segura! Debe producir un valor que se adhiera a la estructura. (String, Map<LocalDate, BigDecimal>), que es lo que produce la consulta. Más en la publicación del blog vinculada.

No puede obtener este nivel de seguridad de tipo (y mapeo) de otro tiempo de ejecución. Una vez que haya extraído la cadena SQL y los valores de vinculación, regrese al nivel de JDBC, donde se desconoce el conjunto de resultados:

  • En JDBC (incluyendo JdbcTemplate), todos ResultSet el contenido es súper genérico. El número de columnas no se conoce, sus posiciones no se conocen, sus tipos de datos no son conocidos por el compilador.
  • En las API de recuperación de DTO de JPA, simplemente obtendrá un Object[], que no es mucho mejor que con JDBC. Diría que es un paso por debajo de JDBC, porque ya ni siquiera obtienes una API.

No tiene que usar la seguridad de tipos de jOOQ todo el tiempo, siempre puede desactivarla, pero al menos, de forma predeterminada, ¡está ahí!

Ejemplo: consultas reactivas

Un buen ejemplo de este tipo de seguridad es cuando se trabaja con R2DBC para ejecutar una consulta reactiva. No creo que nadie prefiera ejecutar la consulta directamente en R2DBC, ya que con jOOQ una consulta simplemente se puede incrustar, por ejemplo, en un reactor. Fluxpara la ejecución automática y el mapeo.

record Table(String schema, String table) {}
 
Flux.from(ctx
        .select(
            INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA,
            INFORMATION_SCHEMA.TABLES.TABLE_NAME)
        .from(INFORMATION_SCHEMA.TABLES))
 
    // Type safe mapping from Record2<String, String> to Table::new
    .map(Records.mapping(Table::new))
    .doOnNext(System.out::println)
    .subscribe();

Cartografía

El ejemplo anterior ya implicaba que el mapeo está disponible automáticamente en jOOQ. Hay muchas formas de mapear un jOOQ Record o entonces Record[N] escriba a un cierto tipo de usuario. Las formas más populares incluyen:

  • Historia DefaultRecordMapperque se basa en la reflexión y utiliza la Result.into(Class) API
  • El mapeador de registro seguro de tipo agregado más recientemente que mapea Record[N] tipos en referencias al constructor (o cualquier otra función), como en el ejemplo anterior.

Pero el mapeo de registros no lo es todo, ¡también está la conversión de tipos de datos!

emulaciones de tiempo de ejecución

Algunas características de SQL se emulan principalmente en tiempo de ejecución cuando se ejecutan consultas con jOOQ. Éstos incluyen:

Estas características que muchos usuarios de jOOQ han llegado a amar no se pueden usar fuera de jOOQ. El SQL generado para estas consultas codifica las colecciones y los registros anidados mediante SQL/XML o SQL/JSON, según el dialecto. Claro, puede volver a implementar la separación de JSON a un objeto Java en su propia capa de acceso a datos, pero ¿por qué? jOOQ funciona muy bien y, como se mencionó anteriormente, es incluso seguro. Si tuviera que volver a implementar esto usted mismo, probablemente no obtendría el mismo nivel de seguridad de tipos.

Otra cosa genial del tiempo de ejecución es:

Que emula automáticamente el procesamiento por lotes de declaraciones SQL consecutivas, sin ninguna intervención de la API.

Tipos definidos por el usuario

Si desea trabajar con tipos definidos por el usuario tanto del lado del servidor como del lado del cliente, todos los enlaces de tipos de datos están integrados en jOOQ y funcionan de manera inmediata. Por ejemplo, en PostgreSQL u Oracle (sintaxis ligeramente diferente):

CREATE TYPE name AS (
  first_name TEXT,
  last_name TEXT
);

CREATE TABLE user (
  id BIGINT PRIMARY KEY,
  name name NOT NULL
);

El generador de códigos no solo obtendrá estos tipos por usted, sino que también puede obtenerlos de forma segura:

Result<Record2<Long, NameRecord>> r =
ctx.select(USER.ID, USER.NAME)
   .from(USER)
   .fetch();

Y luego, por supuesto, aplique un mapeo reflexivo o con seguridad de tipos a ese registro, según sus preferencias. No creo que tal compatibilidad con UDT funcione tan bien con otros tiempos de ejecución. Podrías intentarlo. Los tipos UDT generados implementan JDBC SQLData, por lo que debería poder vincularlos a una declaración JDBC lista para usar. Pero todavía hay casos marginales.

Procedimientos almacenados

Restrictivo OUT o entonces IN OUT Los parámetros son un poco complicados a través de las API de nivel inferior de JDBC, R2DBC o JPA. ¿Por qué no simplemente usar jOOQ, nuevamente, para ejecutar una llamada de procedimiento almacenado? Dado:

CREATE OR REPLACE PROCEDURE my_proc (
  i1 NUMBER,
  io1 IN OUT NUMBER,
  o1 OUT NUMBER,
  o2 OUT NUMBER,
  io2 IN OUT NUMBER,
  i2 NUMBER
) IS
BEGIN
  o1 := io1;
  io1 := i1;
 
  o2 := io2;
  io2 := i2;
END my_proc;

¿Que prefieres? esto (JDBC)?

try (CallableStatement s = c.prepareCall(
    "{ call my_proc(?, ?, ?, ?, ?, ?) }"
)) {
 
    // Set all input values
    s.setInt(1, 1); // i1
    s.setInt(2, 2); // io1
    s.setInt(5, 5); // io2
    s.setInt(6, 6); // i2
 
    // Register all output values with their types
    s.registerOutParameter(2, Types.INTEGER); // io1
    s.registerOutParameter(3, Types.INTEGER); // o1
    s.registerOutParameter(4, Types.INTEGER); // o2
    s.registerOutParameter(5, Types.INTEGER); // io2
 
    s.executeUpdate();
 
    System.out.println("io1 = " + s.getInt(2));
    System.out.println("o1 = " + s.getInt(3));
    System.out.println("o2 = " + s.getInt(4));
    System.out.println("io2 = " + s.getInt(5));
}

¿Donde?

// Short form, passing arguments by index (type safe):
MyProc result = Routines.myProc(configuration, 1, 2, 5, 6);

// Explicit form, passing arguments by name (type safe):
MyProc call = new MyProc();
call.setI1(1);
call.setIo1(2);
call.setIo2(5);
call.setI2(6);
call.execute(configuration);
 
System.out.println("io1 = " + call.getIo1());
System.out.println("o1 = " + call.getO1());
System.out.println("o2 = " + call.getO2());
System.out.println("io2 = " + call.getIo2());

Esta comparación se vuelve aún más obvia cuando se intenta llamar a procedimientos almacenados que aceptan/devuelven tipos definidos por el usuario.

Recuperación de valores de identidad

¡Es tan doloroso en los dialectos SQL y los controladores JDBC! Algunos dialectos SQL tienen soporte nativo, incluyendo:

  • Qb2, H2: FINAL TABLE (la tabla delta de cambio de datos)
  • Firebird, MariaDB, Oracle, PostgreSQL: RETURNING (aunque en Oracle hay muchos retos)
  • Servidor SQL: OUTPUT

Pero de lo contrario, a menudo se deben ejecutar múltiples consultas o se debe usar una API JDBC alternativa. Si quieres probar el arduo trabajo que jOOQ hace por ti, echa un vistazo aquí.

CRUD simple

En caso de que esté utilizando JPA, probablemente no sea la característica principal de jOOQ, porque JPA es un ORM más sofisticado que jOOQ, asignando asociaciones y todo. Pero si no usa JPA (por ejemplo, JdbcTemplate o JDBC directamente), puede escribir de forma muy repetitiva INSERT, UPDATE, DELETE, MERGE declaraciones, cuestionando las opciones de vida, en lugar de simplemente usar la API jOOQ para CRUD usando el UpdatableRecord API.

Manual DML tiene su lugar, especialmente para el procesamiento masivo de datos, pero aparte de eso, ¿cuál prefieres?

IF new_record THEN
  INSERT INTO t (a, b, c) VALUES (1, 2, 3) RETURNING id INTO :id;
ELSE
  UPDATE t SET a = 1, b = 2, c = 3 WHERE id = :id;
END IF;

O solo:

t.setA(1);
t.setB(2);
t.setC(3);
t.store();

Además, tu TRecord por supuesto, se genera y se puede importar desde JSON u otro, ¡vea a continuación!

Importación y exportación de datos

jOOQ admite la importación/exportación de datos listos para usar desde/hacia muchos formatos de datos, que incluyen:

Mejores valores predeterminados

En comparación con JDBC, jOOQ implementa mejores valores predeterminados para la mayoría de los desarrolladores. Esto no significa que JDBC se haya equivocado. JDBC tomó las decisiones correctas para el propósito para el que fue diseñado: un SPI de abstracción de protocolo de red de bajo nivel. Para jOOQ, usar JDBC bajo el capó ha sido súper poderoso.

Pero para los usuarios, es molesto que todo sea siempre:

Lo anterior lleva a muchos de:

  • Gestión de recursos con try-with-resources
  • Reutilización manual de recursos, como PreparedStatementque produce código con estado difícil de mantener

Con jOOQ, todo lo que produce una consulta se recupera ansiosamente en la memoria de forma predeterminada, que es lo que necesitan la mayoría de los usuarios, lo que permite un cierre de recursos más rápido (incluido ResultSet, Statement, Connection, Entre bastidores). Por supuesto, siempre puede optar por el procesamiento de datos de transmisión diferida si lo necesita, ¡incluido el uso reactivo de R2DBC!

Mucho más

Hay muchos más, que vale la pena mencionar:

Prácticamente ningún beneficio de correr fuera de jOOQ

Como prometí, quería explicar por qué no hay casi ningún beneficio en ejecutar fuera de jOOQ a menos que desee obtener datos en una entidad JPA, en caso de que necesite que JPA administre el ciclo de vida de la entidad por usted.

Pero al recuperar DTO, no se beneficia de usar JPA para ejecutar una consulta jOOQ. Es muy fácil dejar que jOOQ ejecute una consulta directamente en una transacción administrada por JPA. El enjuague es necesario de todos modos, por lo que no hay ningún beneficio. Aparte de eso, JPA, JDBC, JdbcTemplate no hacen nada:

  • Este jOOQ no puede hacerlo tan bien o mejor
  • Lo que jOOQ no encaja (transacciones, ciclo de vida de conexión, mapeo, etc.)

jOOQ se puede usar como reemplazo de cualquier otra forma de ejecutar una consulta SQL basada en valores, es decir, siempre que asigne datos a DTO en lugar de entidades. Puede asignar datos a cualquier estructura de datos de destino, incluida cualquier forma de DTO (POJO clásico, registros Java 16, clases de datos kotlin, clases de casos Scala, etc., etc.) o XML, JSON, CSV como se vio anteriormente.

De hecho, lo más probable es que elimine toneladas de texto repetitivo si cambia a jOOQ desde el código de búsqueda y mapa de nivel inferior anterior.

Conclusión

Al igual que el artículo anterior que explica por qué debería usar jOOQ con la generación de código, este artículo debería haberlo convencido de aprovechar todos los beneficios de jOOQ, no solo la creación de consultas. Mucho pensamiento (y quiero decir UN MONTÓN) participó en el diseño de estas funciones y API. Estoy seguro de que los encontrará mejores que la plomería manual una vez que lo domine.

Si quieres conocer otros artículos parecidos a Las muchas razones por las que debe ejecutar sus consultas jOOQ con jOOQ puedes visitar la categoría Código.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Esta página web utiliza cookies para analizar de forma anónima y estadística el uso que haces de la web, mejorar los contenidos y tu experiencia de navegación. Para más información accede a la Política de Cookies . Ver mas