# Crear el proyecto Spring Boot


## Configurar el proyecto

Crea un proyecto Spring Boot desde [Spring Initializr](https://start.spring.io)
seleccionando Maven como herramienta de construcción y agregando los starters **JOOQ Access Layer**,
**Flyway Migration**, **Spring Boot DevTools**, **PostgreSQL Driver** y
**Testcontainers**.

Alternativamente, puedes clonar el
[repositorio de la guía](https://github.com/testcontainers/tc-guide-working-with-jooq-flyway-using-testcontainers).

jOOQ (jOOQ Object Oriented Querying) proporciona una API fluida para construir
consultas SQL seguras para tipos. Para obtener el máximo beneficio de su DSL seguro
para tipos, debes generar código Java a partir de las tablas, vistas y otros objetos
de tu base de datos.

> [!TIP]
> Para obtener más información sobre cómo ayuda el generador de código de jOOQ, lee
> [Por qué deberías usar jOOQ con generación de código](https://blog.jooq.org/why-you-should-use-jooq-with-code-generation/) (en inglés).

El proceso típico para construir y probar la aplicación con generación de código de
jOOQ es:

1. Crear una instancia de base de datos usando Testcontainers.
2. Aplicar las migraciones de base de datos de Flyway.
3. Ejecutar el generador de código de jOOQ para producir código Java a partir de los objetos de la base de datos.
4. Ejecutar pruebas de integración.

El
[testcontainers-jooq-codegen-maven-plugin](https://github.com/testcontainers/testcontainers-jooq-codegen-maven-plugin)
automatiza esto como parte de la construcción de Maven.

## Crear scripts de migración de Flyway

La aplicación de ejemplo tiene las tablas `users`, `posts` y `comments`. Crea
el primer script de migración siguiendo la convención de nomenclatura de Flyway.

Crea `src/main/resources/db/migration/V1__create_tables.sql`:

```sql
create table users
(
    id         bigserial not null,
    name       varchar   not null,
    email      varchar   not null,
    created_at timestamp,
    updated_at timestamp,
    primary key (id),
    constraint user_email_unique unique (email)
);

create table posts
(
    id         bigserial                    not null,
    title      varchar                      not null,
    content    varchar                      not null,
    created_by bigint references users (id) not null,
    created_at timestamp,
    updated_at timestamp,
    primary key (id)
);

create table comments
(
    id         bigserial                    not null,
    name       varchar                      not null,
    content    varchar                      not null,
    post_id    bigint references posts (id) not null,
    created_at timestamp,
    updated_at timestamp,
    primary key (id)
);

ALTER SEQUENCE users_id_seq RESTART WITH 101;
ALTER SEQUENCE posts_id_seq RESTART WITH 101;
ALTER SEQUENCE comments_id_seq RESTART WITH 101;
```

Los valores de las secuencias se reinician en 101 para que puedas insertar
datos de prueba con valores de clave primaria explícitos.

## Configurar la generación de código de jOOQ

Agrega el `testcontainers-jooq-codegen-maven-plugin` a `pom.xml`:

```xml
<properties>
    <testcontainers.version>2.0.4</testcontainers.version>
    <testcontainers-jooq-codegen-maven-plugin.version>0.0.4</testcontainers-jooq-codegen-maven-plugin.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers-jooq-codegen-maven-plugin</artifactId>
            <version>${testcontainers-jooq-codegen-maven-plugin.version}</version>
            <dependencies>
                <dependency>
                    <groupId>org.testcontainers</groupId>
                    <artifactId>testcontainers-postgresql</artifactId>
                    <version>${testcontainers.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.postgresql</groupId>
                    <artifactId>postgresql</artifactId>
                    <version>${postgresql.version}</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <id>generate-jooq-sources</id>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    <phase>generate-sources</phase>
                    <configuration>
                        <database>
                            <type>POSTGRES</type>
                            <containerImage>postgres:16-alpine</containerImage>
                        </database>
                        <flyway>
                            <locations>
                                filesystem:src/main/resources/db/migration
                            </locations>
                        </flyway>
                        <jooq>
                            <generator>
                                <database>
                                    <includes>.*</includes>
                                    <excludes>flyway_schema_history</excludes>
                                    <inputSchema>public</inputSchema>
                                </database>
                                <target>
                                    <packageName>com.testcontainers.demo.jooq</packageName>
                                    <directory>target/generated-sources/jooq</directory>
                                </target>
                            </generator>
                        </jooq>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
```

Esto es lo que hace la configuración del plugin:

- La sección `<configuration>/<database>` establece el tipo de base de datos en
  `POSTGRES` y la imagen de Docker en `postgres:16-alpine`.
- La sección `<configuration>/<flyway>` apunta a los scripts de migración de
  Flyway.
- La sección `<configuration>/<jooq>` configura el nombre del paquete y el
  directorio de salida para el código generado. Puedes utilizar cualquier opción de
  configuración que admita el plugin oficial `jooq-code-generator`.

Al ejecutar `./mvnw clean package`, el plugin utiliza Testcontainers para
iniciar un contenedor de PostgreSQL, aplica las migraciones de Flyway y genera
el código Java en `target/generated-sources/jooq`.

## Crear clases de modelo

Crea clases de modelo para representar las estructuras de datos de varios casos
de uso. Estos records contienen un subconjunto de valores de columna de las tablas.

`User.java`:

```java
package com.testcontainers.demo.domain;

public record User(Long id, String name, String email) {}
```

`Post.java`:

```java
package com.testcontainers.demo.domain;

import java.time.LocalDateTime;
import java.util.List;

public record Post(
  Long id,
  String title,
  String content,
  User createdBy,
  List<Comment> comments,
  LocalDateTime createdAt,
  LocalDateTime updatedAt
) {}
```

`Comment.java`:

```java
package com.testcontainers.demo.domain;

import java.time.LocalDateTime;

public record Comment(
  Long id,
  String name,
  String content,
  LocalDateTime createdAt,
  LocalDateTime updatedAt
) {}
```

## Implementar repositorios utilizando jOOQ

Crea `UserRepository.java` con métodos para crear un usuario y buscar un usuario
por correo electrónico:

```java
package com.testcontainers.demo.domain;

import static com.testcontainers.demo.jooq.tables.Users.USERS;
import static org.jooq.Records.mapping;

import java.time.LocalDateTime;
import java.util.Optional;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;

@Repository
class UserRepository {

  private final DSLContext dsl;

  UserRepository(DSLContext dsl) {
    this.dsl = dsl;
  }

  public User createUser(User user) {
    return this.dsl.insertInto(USERS)
      .set(USERS.NAME, user.name())
      .set(USERS.EMAIL, user.email())
      .set(USERS.CREATED_AT, LocalDateTime.now())
      .returningResult(USERS.ID, USERS.NAME, USERS.EMAIL)
      .fetchOne(mapping(User::new));
  }

  public Optional<User> getUserByEmail(String email) {
    return this.dsl.select(USERS.ID, USERS.NAME, USERS.EMAIL)
      .from(USERS)
      .where(USERS.EMAIL.equalIgnoreCase(email))
      .fetchOptional(mapping(User::new));
  }
}
```

El DSL de jOOQ se parece a SQL pero está escrito en Java. Dado que el código se
genera a partir del esquema de la base de datos, se mantiene sincronizado con la
estructura de esta y proporciona seguridad de tipos. Por ejemplo,
`where(USERS.EMAIL.equalIgnoreCase(email))` espera un valor de tipo `String`. Si
pasas un valor que no sea una cadena de texto, como `123`, obtendrás un error de
compilación.

## Recuperar grafos de objetos complejos

jOOQ brilla cuando se trata de consultas complejas. La base de datos tiene una
relación de muchos a uno de `Post` a `User` y una relación de uno a muchos de `Post`
a `Comment`.

Crea `PostRepository.java` para cargar un `Post` con su creador y comentarios
utilizando una sola consulta con la característica MULTISET de jOOQ:

```java
package com.testcontainers.demo.domain;

import static com.testcontainers.demo.jooq.Tables.COMMENTS;
import static com.testcontainers.demo.jooq.tables.Posts.POSTS;
import static org.jooq.Records.mapping;
import static org.jooq.impl.DSL.multiset;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;

import java.util.Optional;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;

@Repository
class PostRepository {

  private final DSLContext dsl;

  PostRepository(DSLContext dsl) {
    this.dsl = dsl;
  }

  public Optional<Post> getPostById(Long id) {
    return this.dsl.select(
        POSTS.ID,
        POSTS.TITLE,
        POSTS.CONTENT,
        row(POSTS.users().ID, POSTS.users().NAME, POSTS.users().EMAIL)
          .mapping(User::new)
          .as("createdBy"),
        multiset(
          select(
            COMMENTS.ID,
            COMMENTS.NAME,
            COMMENTS.CONTENT,
            COMMENTS.CREATED_AT,
            COMMENTS.UPDATED_AT
          )
            .from(COMMENTS)
            .where(POSTS.ID.eq(COMMENTS.POST_ID))
        )
          .as("comments")
          .convertFrom(r -> r.map(mapping(Comment::new))),
        POSTS.CREATED_AT,
        POSTS.UPDATED_AT
      )
      .from(POSTS)
      .where(POSTS.ID.eq(id))
      .fetchOptional(mapping(Post::new));
  }
}
```

Esto utiliza los
[registros anidados](https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/nested-records/)
de jOOQ para la asociación de muchos a uno de `Post`-a-`User` y
[MULTISET](https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/multiset-value-constructor/)
para la asociación de uno a muchos de `Post`-a-`Comment`.

