Spring Boot series: autoconfiguración con jOOQ

Hace ya como un año publiqué un post en el que explicaba como configurar un proyecto con jOOQ, que es una librería de acceso a base de datos. Vuelvo con ello, esta vez con Spring Boot y su sencillez 🙂

Concepto:
Con la nueva versión de Spring Boot, la 1.3, aparte de cosas como las developer tools, se han añadido nuevas ‘autoconfiguraciones’, entre ellas, para jOOQ. En este post veremos como configurar fácilmente jOOQ en un proyecto Spring Boot. ¡Al lío!

Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 15.0.2 Ultimate version

Pasos:

1. Crearemos un nuevo proyecto Maven básico con Spring Boot igual que siempre. Nuestro pom inicial quedaría así:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edwise.springbootseries.jooq</groupId>
    <artifactId>springboot-series-jooq</artifactId>
    <version>0.1.0</version>
    <packaging>jar</packaging>

    <name>springboot-series-jooq</name>
    <description>Test project for Spring Boot new featture: jooq autoconfig</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.3.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <testSource>${java.version}</testSource>
                    <testTarget>${java.version}</testTarget>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Para este caso no he añadido el starter web, simplemente el de logging para tener logback y slf4j. Así como el starter de tests, y assertj.
Añadimos también nuestra clase main para Spring Boot.

2. Añadimos ahora a nuestro pom la dependencia del nuevo ‘starter’ para jooq. Añadimos también la de H2 para usarlo como base de datos:

...
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jooq</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
...

Y con esto ya casi está. Spring Boot configurará jOOQ con valores por defecto y tendremos disponible un bean de la clase DSLContext.
Solo nos falta el código generado por jOOQ para las tablas de nuestra BD. Vamos a generarlos.

3. Para hacer más sencilla la ejecución de nuestra aplicación, primero vamos a hacer unos scripts sql para la creación y borrado de nuestra base de datos, y los ejecutaremos con el plugin ‘sql-maven-plugin‘ de maven.

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>sql-maven-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <version>${h2.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <driver>org.h2.Driver</driver>
                    <url>jdbc:h2:${basedir}/target/springbootseriesjooqDB</url>
                    <srcFiles>
                        <srcFile>${basedir}/src/main/resources/delete.sql</srcFile>
                        <srcFile>${basedir}/src/main/resources/schema.sql</srcFile>
                        <srcFile>${basedir}/src/main/resources/data.sql</srcFile>
                    </srcFiles>
                </configuration>
            </plugin>

Podéis consultar los tres ficheros .sql aquí. No tienen mucho misterio.

4. Ahora añadimos a nuestro pom.xml el plugin de jOOQ para la generación de su código:

            <plugin>
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <version>${h2.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <jdbc>
                        <driver>org.h2.Driver</driver>
                        <url>jdbc:h2:${basedir}/target/springbootseriesjooqDB</url>
                    </jdbc>
                    <generator>
                        <name>org.jooq.util.DefaultGenerator</name>
                        <database>
                            <name>org.jooq.util.h2.H2Database</name>
                            <includes>.*</includes>
                            <excludes/>
                            <inputSchema>PUBLIC</inputSchema>
                        </database>
                        <target>
                            <packageName>sample.jooq.domain</packageName>
                            <directory>${basedir}/generated-jooq-code/main/java</directory>
                        </target>
                    </generator>
                </configuration>
            </plugin>

El plugin es similar a como lo hicimos en el post anterior de jOOQ:
– Asignamos la ejecución del plugin al ‘goal’ generate de maven.
– Añadimos la dependencia de H2.
– En la configuración le pasamos la url de base de datos h2 (línea 21), la misma que en el sql-maven-plugin, y en la información para la generación de código, la ruta donde generarlo (línea 33).

Con ejecutar ahora un ‘mvn package’ tendremos generado el código de jOOQ.

5. Vamos ahora a probarlo. Para ello creamos un DAO simplón, anotado como @Repository, tal que así:

package com.edwise.springbootseries.jooq.dao;

// imports...

@Repository
public class BooksDao {
    private static final Logger LOG = LoggerFactory.getLogger(BooksDao.class);

    private final DSLContext create;

    @Autowired
    public BooksDao(DSLContext dslContext) {
        this.create = dslContext;
    }

    public Result<Record> getAllBookCharacters() {
        Result<Record> result = create
                .select()
                .from(Tables.BOOK_CHARACTER)
                .fetch();

        LOG.info("Resultado query getAllBookCharacters: \n{}", result);
        return result;
    }

    public Result<Record> getAllBookCharactersOrderByName() {
        Result<Record> result = create
                .select()
                .from(Tables.BOOK_CHARACTER)
                .orderBy(Tables.BOOK_CHARACTER.NAME)
                .fetch();

        LOG.info("Resultado query getAllBookCharactersOrderByName: \n{}", result);
        return result;
    }

    public Optional<Record> getBookCharacterById(int id) {
        Optional<Record> result = create
                .select()
                .from(Tables.BOOK_CHARACTER)
                .where(Tables.BOOK_CHARACTER.ID.equal(id))
                .orderBy(Tables.BOOK_CHARACTER.NAME)
                .fetchOptional();

        LOG.info("Resultado query getBookCharacterById: \n{}",s result);
        return result;
    }
}

Lo que hacemos es nada más que injectar el bean DSLContext (por contructor). Implementamos también varios métodos para recuperar distinta información de nuestra base de datos, usando la sintaxis de jOOQ.
No necesitamos abrir la conexión ni instanciar nada, como hacíamos en el post de jOOQ. Todo nos lo gestiona Spring Boot.

6. Para automatizar la prueba, he implementado un test de integración (carga todo el contexto de Spring) para probar nuestro DAO y ver que jOOQ funciona:

package com.edwise.springbootseries.jooq.dao;

// imports...

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class BooksDaoIT {
    private static final Logger LOG = LoggerFactory.getLogger(BooksDaoIT.class);

    private static final int EXISTING_CHAR_ID = 4;
    private static final int NOT_EXISTING_CHAR_ID = 13;

    @Autowired
    private BooksDao booksDao;

    @Test
    public void getAllBookCharactersShouldReturnAllTheChars() {
        Result<Record> allBookCharacters = booksDao.getAllBookCharacters();

        allBookCharacters.stream()
                .map(record -> record.getValue(Tables.BOOK_CHARACTER.NAME))
                .forEach(name -> LOG.info("Char: {}", name));

        assertThat(allBookCharacters)
                .extracting(record -> record.getValue(Tables.BOOK_CHARACTER.NAME))
                .contains("Samwise", "Gandalf", "Frodo", "Saruman", "Aragorn");
    }

    @Test
    public void getAllBookCharactersOrderByNameShouldReturnCharsSorted() {
        Result<Record> allBookCharactersByName =
                booksDao.getAllBookCharactersOrderByName();

        assertThat(allBookCharactersByName)
                .extracting(record -> record.getValue(Tables.BOOK_CHARACTER.NAME))
                .isSorted();
    }

    @Test
    public void getBookByIdThatExistsShouldReturnValidRecord() {
        Optional<Record> bookCharacter =
                booksDao.getBookCharacterById(EXISTING_CHAR_ID);

        assertThat(
                bookCharacter
                        .orElseThrow(() -> new AssertionError("Not existing character"))
                        .getValue(Tables.BOOK_CHARACTER.NAME))
                .isEqualTo("Samwise");
    }

    @Test
    public void getBookByIdThatNotExistsShouldReturnEmptyOptionalRecord() {
        Optional<Record> bookCharacter =
                booksDao.getBookCharacterById(NOT_EXISTING_CHAR_ID);

        assertThat(bookCharacter.isPresent()).isFalse();
    }
}

Como vemos, Spring Boot facilita mucho la integración con jOOQ, gestionandolo, como siempre, con autoconfiguraciones por defecto. Si por lo que sea necesitamos configurar jOOQ con algún parametro o configuración especial, podemos hacerlo con propiedades del tipo ‘spring.jooq.propiedad’ en el appication.properties, así como definir nuestro propio @Bean de la clase de configuración de jOOQ (org.jooq.Configuration).

Como siempre, tenéis el código disponible en mi github, proyecto springboot-series-jooq.

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’ 🙂