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

Implementar la capa de base de datos con Spring Data

En este post voy a montar otro PoC, esta vez es el turno de Spring Data, que junto con Spring Boot y sus autoconfiguraciones nos va a simplificar mucho el código necesario para el acceso a base de datos. El ejemplo lo haremos sobre un ejemplo de servicio REST sobre una aplicación Spring Boot que ya hice en un post anterior.

Concepto:
Spring Data es otro de los proyectos de Spring, cuyo objetivo es simplificar el desarrollo de la capa de base de datos en nuestra aplicación. En concreto, Spring Data JPA realmente es una capa de abstracción más, montada sobre JPA, para evitarnos al máximo posible el código boilerplate necesario en cualquier aplicación que use la especificación JPA para el acceso a base de datos.

La herramienta principal son los repositorios (Repository). Spring nos provee de varias interfaces que simplemente debemos heredar (con una interfaz nuestra), y que contienen los métodos necesarios para el acceso a base de datos. Spring, por su parte, se encarga de implementar esa interfaz que hayamos creado.

En el ejemplo que vamos a seguir en este post, le implementaremos la capa de base de datos al servicio REST que ya implementamos en el post Implementar un servicio Rest con Spring.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.4
IDE Intellij 14.0.1 Ultimate version

Pasos:

1. Vamos a usar como base el proyecto de ejemplo usado en el post del servicio REST. Antes de empezar, resumo algunos cambios iniciales que le he hecho al proyecto:
– Campo fecha joda-time de User cambiado a Java 8 Date API (JSR310), así como el bean de jackson para ese parseo.
– Modificado la url del servicio a ‘/api/users’ (antes era ‘/user’)
– Modificados nombre de paquetes (estaban en plural)
– Eliminado método ‘update’ del UserService, ahora se usa ‘save’ tanto para actualizar como crear
– Nuevo método ‘existsUser’ en el UserService.
– Otras pequeñas mejoras en la implementación del controller, respecto al update y delete.

2. Como comenté arriba, usaremos como base de datos H2, una base de datos «embebida», perfecta para pocs, test de integración y demás. Para usarla, con añadir la dependencia al pom, es suficiente. La autoconfiguración de Spring Boot, hará el resto (le pasará los parámetros por defecto para el datasource).

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

Añadimos también al pom la dependencia para spring-data-jpa, en concreto el ‘starter’ de spring boot para la misma:

...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
...

3. Anotamos nuestra entity con las anotaciones básicas para persistencia, quedaría así:

package com.edwise.pocs.springdata.entity;

// imports...

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    private Integer type;
    private String phone;
    private LocalDate birthDate;

    // getter and setters...

    public User copyFrom(User user) {
        if (user.name != null) {
            this.name = user.name;
        }
        if (user.type != null) {
            this.type = user.type;
        }
        if (user.phone != null) {
            this.phone = user.phone;
        }
        if (user.birthDate != null) {
            this.birthDate = user.birthDate;
        }
        return this;
    }

    // equals, hashcode and toString methods...
}

Explicación rápida de las anotaciones usadas:
Entity: marca nuestra entity como eso, una entity de base de datos, para que sea gestionada como tal.
Table: el nombre de la tabla de base de datos. Si no lo ponemos, tiene que ser igual al nombre de la clase.
Id: marcamos el atributo que será la primary key con esta anotación
GeneratedValue: Como se genera la primary key.

4. Creamos un repositorio de Spring Data. Para ello simplemente creamos una interfaz que herede de JpaRepository (como parámetros genéricos, nuestro entity y Long por el tipo de la primary key).

package com.edwise.pocs.springdata.repository;

// imports

public interface UserRepository extends JpaRepository<User, Long> {

}

El código está correcto, es una interfaz vacía. Con esto es suficiente, ya tenemos implementada nuestra capa de base de datos. Al crear esa interfaz, es Spring quien se encargará de implementarla. Simplemente tenemos que usarla, obteniéndola como cualquier bean del contexto de Spring.
JpaRepository hereda, entre otras de CrudRepository, que tiene los métodos básicos para implementar un CRUD (getAll, save, delete…).

5. Enganchamos ahora el repositorio con nuestro UserService. Nuestra implementación era antes un mock (UserServiceMock.java), lo renombramos (como UserServiceImpl) y lo modificamos para que use nuestro repositorio para las distintas operaciones, quedando:

package com.edwise.pocs.springdata.service.impl;

// imports...

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User findById(Long id) {
        return userRepository.findOne(id);
    }

    @Override
    public List<User> findAll() {
        return userRepository.findAll();
    }

    @Override
    public User save(User user) {
        return userRepository.save(user);
    }

    @Override
    public void delete(Long id) {
        userRepository.delete(id);
    }

    @Override
    public boolean existsUser(Long id) {
        return userRepository.exists(id);
    }
}

Simplemente obtenemos con Autowired el repository, y llamamos en cada método de nuestro service al método que corresponda. Nada raro.

6. ¡Terminado! Como siempre, ejecutamos ‘mvn spring-boot:run’ en una consola o desde la tool window de maven en Intellij, para arrancar nuestra aplicación. Si accedemos con un cliente REST a http://localhost:8080/api/users responderá nuestro servicio. Como la base de datos está vacia, lo mejor es hacer algún POST primero para meter algún user, el formato del body de la petición sería algo así:

{
  "name": "Triss",
  "type": 2,
  "phone": "645338822",
  "birthDate": "1985-06-19"
}

7. Más opciones de Spring Data: lo que hemos implementado es lo básico, y lo más sencillo. Quizá, por ejemplo, necesitaríamos un método que nos devuelva los usuarios de un determinado tipo (campo ‘type’). Muy fácil también, añadiríamos el siguiente método en nuestro repository:

public interface UserRepository extends JpaRepository<User, Long> {

    public List<User> findByType(Integer type);    
}

Este método también se encargará el propio Spring de implementarlo. El truco está en que la nomenclatura del método debe ser siempre ‘findByField’ donde ‘Field’ es el nombre del atributo por el que filtramos (siguiendo camelcase), y debe tener un parámetro que sea del tipo del atributo por el que filtrar.

8. Queries más complejas: lo ideal es implementar todos nuestros métodos extra de la manera anterior, pero quizá necesitemos algo más concreto. Por ejemplo, quizá necesitemos un método que devuelva la lista de usuarios para un nombre determinado, sin tener en cuenta mayúsculas o minúsculas. Inicialmente no podemos hacer eso con la estrategia anterior, así que tendríamos que picarnos la query, por ejemplo, con una NamedQuery de JPA añadiendo la anotación correspondiente en nuestra Entity:

@Entity
@Table(name = "users")
@NamedQuery(name = "User.findByNameIgnoreCase", query = "SELECT u FROM User u WHERE LOWER(u.name) = LOWER(?1)")
public class User {
    // all code...
}

Y el siguiente método en nuestro repository:

public interface UserRepository extends JpaRepository<User, Long> {

    public List<User> findByType(Integer type);

    public List<User> findByNameIgnoreCase(String name);   
}

Para que funcione, es necesario, como se ve en el código, que el ‘name’ de la namedQuery contenga el nombre del método del Repository en el que queremos que se ejecute.

9. Similar a lo anterior, podemos usar la anotación Query de Spring Data, directamente modificando solo el repository. Por ejemplo, un método que devuelva la lista de usuarios dado el prefijo de un teléfono, sería añadir el siguiente método a nuestro UserRepository:

public interface UserRepository extends JpaRepository<User, Long> {

    public List<User> findByType(Integer type);

    public List<User> findByNameIgnoreCase(String name);

    @Query("SELECT u FROM User u WHERE u.phone LIKE CONCAT(:phonePrefix, '%')")
    public List<User> findByPhonePrefix(@Param("phonePrefix") String phonePrefix);
}

Como se puede ver, con la anotación @Param marcamos el nombre del parámetro dentro de la query.

Hay más posibilidades: Criteria Queries, QueryDSL…, pero lo vamos a dejar aquí. Si queréis descargaros el proyecto completo, está en mi github, proyecto spring-data-example. También he añadido, comentado, el código necesario en el pom y en un properties para poder hacer la prueba conectando contra un MySQL. Jugad con él 🙂