Como configurar jOOQ en un proyecto maven

En este post voy a explicar como montar un sencillo PoC con la librería jOOQ, para el acceso a base de datos usando queries SQL «implementadas» directamente con clases y métodos Java. La idea es detallar los pasos para poder usarlo, en un proyecto maven. Más adelante es probable que escriba otro post explicando como integrarlo con Spring.

Concepto:
jOOQ (Java Object Oriented Querying) es una librería Java para el acceso a base de datos cuya principal característica es que define un DSL en java, con fluent interface, para la generación de queries, tal cual las generaríamos en SQL.

Podemos verlo con un ejemplo. Esto sería una query en SQL:

SELECT BOOK_CHARACTER.NAME
FROM BOOK_CHARACTER
WHERE BOOK_CHARACTER.ID = 1

Y esto sería con jOOQ, en java:

DSL.using(connection)
    .select(Tables.BOOK_CHARACTER.NAME)
    .from(Tables.BOOK_CHARACTER)
    .where(Tables.BOOK_CHARACTER.ID.equal(1))
    .fetch();

Su principal ventaja es la seguridad que ofrece: mientras que al picarnos una query SQL y lanzarla desde Java con JDBC o similar, hasta que no la ejecutamos, no podemos estar seguro de que no hemos metido la pata en la query. Con jOOQ, cualquier error sintáctico o de tipos es «controlado» por el compilador. Es decir, si la query no es correcta… ¡no compila!

El PoC que voy a montar va a ser una aplicación maven simple en la que usaremos H2 como base de datos y usaré logback para logs. En el voy a mostrar principalmente como se configura jOOQ.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.0.2 Ultimate version

Pasos:

1. Crearemos un nuevo proyecto Maven igual que lo creamos en mi primer post de Spring Boot desde Intellij (pasos 1 al 4). Podemos hacerlo con el archetype maven ‘quickstart’. Yo para este PoC he usado un archetype creado por mi que es un quickstart actualizado. Os lo podeís descargar de mi github: https://github.com/edwise/maven-archetypes

2. Añadimos a nuestro pom.xml las dependencias necesarias, tanto de jOOQ como de H2, logback y slfj4 ( estos dos últimos para logs). Serían todas estas:

...
<dependencies>
    <dependency>
        <groupId>org.jooq</groupId>
        <artifactId>jooq</artifactId>
        <version>3.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.jooq</groupId>
        <artifactId>jooq-meta</artifactId>
        <version>3.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.jooq</groupId>
        <artifactId>jooq-codegen</artifactId>
        <version>3.5.1</version>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.182</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.10</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.2</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.1.2</version>
    </dependency>
    ...
</dependencies> 
...

3. Dado que vamos a usar H2 como base de datos, y en memoria, vamos a picarnos un pequeño script para recrear la base de datos cada vez que conectamos a ella. Creamos una subcarpeta ‘resources’ y en ella creamos el siguiente fichero:

create schema if not exists pocjooq;

use schema pocjooq;

create table if not exists book_character (id integer primary key, name varchar(30));

delete from book_character;
insert into book_character values (1, 'Saruman');
insert into book_character values (2, 'Gandalf');
insert into book_character values (3, 'Aragorn');
insert into book_character values (4, 'Samwise');
insert into book_character values (5, 'Frodo');

commit;

4. Ahora vamos a hacer que jOOQ autogenere sus clases. jOOQ realmente lo que hace es leer toda la estructura de nuestra base de datos y generar código y clases basado en nuestras tablas y columnas. Se puede hacer de varias maneras (más info en su documentación), pero para nuestro caso lo vamos a hacer con el plugin maven de jOOQ. Añadimos lo siguiente a nuestro pom.xml:

...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.2</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <testSource>1.8</testSource>
                <testTarget>1.8</testTarget>
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.jooq</groupId>
            <artifactId>jooq-codegen-maven</artifactId>
            <version>3.5.1</version>

            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
            </executions>

            <dependencies>
                <dependency>
                    <groupId>com.h2database</groupId>
                    <artifactId>h2</artifactId>
                    <version>1.4.182</version>
                </dependency>
            </dependencies>

            <configuration>
                <jdbc>
                    <driver>org.h2.Driver</driver>
                    <url>jdbc:h2:mem:pocjooq;INIT=RUNSCRIPT FROM 'src/main/resources/populate_db.sql'</url>
                    <user>sa</user>
                    <password></password>
                </jdbc>

                <generator>
                    <name>org.jooq.util.JavaGenerator</name>
                    <database>
                        <name>org.jooq.util.h2.H2Database</name>
                        <includes>.*</includes>
                        <excludes></excludes>
                        <inputSchema>POCJOOQ</inputSchema>
                    </database>
                    <target>
                        <packageName>org.jooq.util.maven.generated</packageName>
                        <directory>target/generated-sources/jooq</directory>
                    </target>
                </generator>
            </configuration>
        </plugin>
    </plugins>
</build>
...

He añadido primero el maven-compiler-plugin, necesario para poder decirle a maven que compile con la JDK 8.
El plugin de jOOQ para la generación de código, jooq-codegen-maven, consta de lo siguiente:
– El ‘goal’ en el que se generará el código (‘generate’ en este caso).
– Dependencias que necesita: en nuestro caso la dependencia de H2 para la base de datos.
– Configuración de acceso a la base de datos (url, user, pass y driver). En nuestro caso, los por defecto de H2 para una base de datos en memoria. En la url de la conexión a H2, le pasamos como script para ejecutar al arrancar, el fichero .sql creado anteriormente.
– Configuración del generador de código de jOOQ (tag ‘generator’): el generador usado (JavaGenerator), información sobre la base de datos: tipo de base de datos, si queremos excluir algo y el schema principal (‘POCJOOQ’ en nuestro caso), así como directorio destino donde se generará el código, y nombre de paquetería.

5. Ahora podemos, o bien ejecutar ese plugin (desde consola con ‘mvn jooq-codegen:generate’ o desde la tool window de maven en intellij), o bien compilar el proyecto completo (con maven también), y generará todas las clases. Podemos acceder a la ruta ‘target/generated-sources/jooq’ para ver el código generado. La que más usaremos será la clase ‘Tables.java’.

6. jOOQ necesita una conexión jdbc para funcionar. Para ello he creado una clase ‘DBConnector.java’, podéis verla directamente en github aquí. No es necesario que entremos mucho en detalle, lo importante es que realiza una conexión con la misma base de datos usada en el plugin, y nos devuelve un clásico ‘java.sql.Connection’. Los métodos para obtener la conexión y para cerrarla son los siguientes:

// Obtener una conexión a la BD
java.sql.Connection connection = DBConnector.getInstance().connection();

// Cerrar la conexión a la BD
DBConnector.getInstance().closeConnection();

7. Con esto ya podemos desarrollar directamente queries en java con jOOQ. Vamos a realizar unos ejemplos, para ello crearemos una clase de test jUnit. Empezaremos implementando los métodos @Before y @After, para obtener la conexión y para cerrarla:

package com.edwise.pocs.jooq;

// imports...

public class JOOQExampleTest {
    private final static Logger log = LoggerFactory.getLogger(JOOQExampleTest.class);

    private DSLContext jooqDSL;

    @Before
    public void setUp() {
        jooqDSL = DSL.using(DBConnector.getInstance().connection(), SQLDialect.H2);
    }

    @After
    public void tearDown() {
        DBConnector.getInstance().closeConnection();
    }
}

Aparte de obtener un logger, declaramos un atributo de tipo DSLContext y lo inicializamos en el método @Before, pasandole la conexión, y el el tipo de base de datos (H2 en este caso). Con ese objeto DSLContext podemos llamar a todos los métodos de jOOQ.
En el método after cerramos la conexión.

8. Implementamos un pequeño ejemplo de una select que nos devuelva todo el contenido de la única tabla de la base de datos, BOOK_CHARACTER:

    @Test
    public void testSelectAll() {
        Result<Record> result = jooqDSL
                .select()
                .from(Tables.BOOK_CHARACTER)
                .fetch();

        log.info("Resultado query: \n{}", result.toString());
    }

Usamos para ello el DSLContext definido, y sobre el, con fluent interface, montamos la query. Para los nombres de tablas o columnas usamos la clase Tables (si importamos estáticamente toda el contenido de esa clase sería incluso más legible el ejemplo).
El resultado nos lo devuelve en un objeto Result. Al ejecutar ese método veremos que jOOQ tiene todos sus ‘toString()’ bastante currados, el log de este método nos mostrará algo como esto:

18:34:06.953 [main] INFO  com.edwise.pocs.jooq.JOOQExampleTest - Resultado query:
+----+-------+
|  ID|NAME   |
+----+-------+
|   1|Saruman|
|   2|Gandalf|
|   3|Aragorn|
|   4|Samwise|
|   5|Frodo  |
+----+-------+

Más aún, como tenemos logback configurado con su configuración por defecto, está activado el nivel DEBUG. jOOQ muestra mucha información a nivel DEBUG. El resultado por consola completo de ejecuta el test anterior muestra, antes de ese log, logs de jOOQ en el que muestra la query resultante al «traducirla» a SQL, el tiempo que le ha llevado, el resultado obtenido, etc.

9. Como último ejemplo, vamos a implementar un test que haga uso de los Streams de Java 8. jOOQ es totalmente compatible con Java 8:

    @Test
    public void testStreamForEachJava8() {
        jooqDSL.selectFrom(Tables.BOOK_CHARACTER)
                .where(Tables.BOOK_CHARACTER.ID.in(2, 3))
                .orderBy(Tables.BOOK_CHARACTER.NAME)
                .fetch()
                .stream()
                .map(bookCharacter -> bookCharacter.getName().toUpperCase())
                .forEach(s -> log.info("Name: {}", s));
    }

En este caso obtenemos solo los registros con ids 2 y 3, los ordenamos por nombre, y mediante el uso de streams lo ponemos en mayúsculas y los logamos.
El resultado por consola es el siguiente:

18:34:23.038 [main] INFO  com.edwise.pocs.jooq.JOOQExampleTest - Name: ARAGORN
18:34:23.038 [main] INFO  com.edwise.pocs.jooq.JOOQExampleTest - Name: GANDALF

No me enrollo más, que el post era solo para configurarlo 😉 . jOOQ por supuesto proporciona métodos para inserts, updates, deletes… El manual es bastante didáctico y completo. Además, en su web, en la sección ‘Community’ tenéis muchos tutoriales, posts, etc. Echadles un vistazo.

Por último, como siempre, podéis descargar el proyecto completo en mi github, proyecto ‘jooq-example’ 🙂